binarylog: implement env variable config parsing (#2299)
This commit is contained in:
141
internal/binarylog/binarylog.go
Normal file
141
internal/binarylog/binarylog.go
Normal file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package binarylog implementation binary logging as defined in
|
||||
// https://github.com/grpc/proposal/blob/master/A16-binary-logging.md.
|
||||
package binarylog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
// Logger is the global binary logger for the binary. One of this should be
|
||||
// built at init time from the configuration (environment varialbe or flags).
|
||||
//
|
||||
// It is used to get a methodLogger for each individual method.
|
||||
var Logger *logger
|
||||
|
||||
func init() {
|
||||
const envStr = "GRPC_BINARY_LOG_FILTER"
|
||||
configStr := os.Getenv(envStr)
|
||||
Logger = newLoggerFromConfigString(configStr)
|
||||
}
|
||||
|
||||
type methodLoggerConfig struct {
|
||||
// Max length of header and message.
|
||||
hdr, msg uint64
|
||||
}
|
||||
|
||||
type logger struct {
|
||||
all *methodLoggerConfig
|
||||
services map[string]*methodLoggerConfig
|
||||
methods map[string]*methodLoggerConfig
|
||||
|
||||
blacklist map[string]struct{}
|
||||
}
|
||||
|
||||
// newEmptyLogger creates an empty logger. The map fields need to be filled in
|
||||
// using the set* functions.
|
||||
func newEmptyLogger() *logger {
|
||||
return &logger{}
|
||||
}
|
||||
|
||||
// Set method logger for "*".
|
||||
func (l *logger) setDefaultMethodLogger(ml *methodLoggerConfig) error {
|
||||
if l.all != nil {
|
||||
return fmt.Errorf("conflicting global rules found")
|
||||
}
|
||||
l.all = ml
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set method logger for "service/*".
|
||||
//
|
||||
// New methodLogger with same service overrides the old one.
|
||||
func (l *logger) setServiceMethodLogger(service string, ml *methodLoggerConfig) error {
|
||||
if _, ok := l.services[service]; ok {
|
||||
return fmt.Errorf("conflicting rules for service %v found", service)
|
||||
}
|
||||
if l.services == nil {
|
||||
l.services = make(map[string]*methodLoggerConfig)
|
||||
}
|
||||
l.services[service] = ml
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set method logger for "service/method".
|
||||
//
|
||||
// New methodLogger with same method overrides the old one.
|
||||
func (l *logger) setMethodMethodLogger(method string, ml *methodLoggerConfig) error {
|
||||
if _, ok := l.blacklist[method]; ok {
|
||||
return fmt.Errorf("conflicting rules for method %v found", method)
|
||||
}
|
||||
if _, ok := l.methods[method]; ok {
|
||||
return fmt.Errorf("conflicting rules for method %v found", method)
|
||||
}
|
||||
if l.methods == nil {
|
||||
l.methods = make(map[string]*methodLoggerConfig)
|
||||
}
|
||||
l.methods[method] = ml
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set blacklist method for "-service/method".
|
||||
func (l *logger) setBlacklist(method string) error {
|
||||
if _, ok := l.blacklist[method]; ok {
|
||||
return fmt.Errorf("conflicting rules for method %v found", method)
|
||||
}
|
||||
if _, ok := l.methods[method]; ok {
|
||||
return fmt.Errorf("conflicting rules for method %v found", method)
|
||||
}
|
||||
if l.blacklist == nil {
|
||||
l.blacklist = make(map[string]struct{})
|
||||
}
|
||||
l.blacklist[method] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMethodLogger returns the methodLogger for the given methodName.
|
||||
//
|
||||
// methodName should be in the format of "/service/method".
|
||||
//
|
||||
// Each methodLogger returned by this method is a new instance. This is to
|
||||
// generate sequence id within the call.
|
||||
func (l *logger) GetMethodLogger(methodName string) *MethodLogger {
|
||||
s, m, err := parseMethodName(methodName)
|
||||
if err != nil {
|
||||
grpclog.Infof("binarylogging: failed to parse %q: %v", methodName, err)
|
||||
return nil
|
||||
}
|
||||
if ml, ok := l.methods[s+"/"+m]; ok {
|
||||
return newMethodLogger(ml.hdr, ml.msg)
|
||||
}
|
||||
if _, ok := l.blacklist[s+"/"+m]; ok {
|
||||
return nil
|
||||
}
|
||||
if ml, ok := l.services[s]; ok {
|
||||
return newMethodLogger(ml.hdr, ml.msg)
|
||||
}
|
||||
if l.all == nil {
|
||||
return nil
|
||||
}
|
||||
return newMethodLogger(l.all.hdr, l.all.msg)
|
||||
}
|
147
internal/binarylog/binarylog_test.go
Normal file
147
internal/binarylog/binarylog_test.go
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package binarylog
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test that get method logger returns the one with the most exact match.
|
||||
func TestGetMethodLogger(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in string
|
||||
method string
|
||||
hdr, msg uint64
|
||||
}{
|
||||
// Global.
|
||||
{
|
||||
in: "*{h:12;m:23}",
|
||||
method: "/s/m",
|
||||
hdr: 12, msg: 23,
|
||||
},
|
||||
// service/*.
|
||||
{
|
||||
in: "*,s/*{h:12;m:23}",
|
||||
method: "/s/m",
|
||||
hdr: 12, msg: 23,
|
||||
},
|
||||
// Service/method.
|
||||
{
|
||||
in: "*{h;m},s/m{h:12;m:23}",
|
||||
method: "/s/m",
|
||||
hdr: 12, msg: 23,
|
||||
},
|
||||
{
|
||||
in: "*{h;m},s/*{h:314;m},s/m{h:12;m:23}",
|
||||
method: "/s/m",
|
||||
hdr: 12, msg: 23,
|
||||
},
|
||||
{
|
||||
in: "*{h;m},s/*{h:12;m:23},s/m",
|
||||
method: "/s/m",
|
||||
hdr: maxUInt, msg: maxUInt,
|
||||
},
|
||||
|
||||
// service/*.
|
||||
{
|
||||
in: "*{h;m},s/*{h:12;m:23},s/m1",
|
||||
method: "/s/m",
|
||||
hdr: 12, msg: 23,
|
||||
},
|
||||
{
|
||||
in: "*{h;m},s1/*,s/m{h:12;m:23}",
|
||||
method: "/s/m",
|
||||
hdr: 12, msg: 23,
|
||||
},
|
||||
|
||||
// With black list.
|
||||
{
|
||||
in: "*{h:12;m:23},-s/m1",
|
||||
method: "/s/m",
|
||||
hdr: 12, msg: 23,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
l := newLoggerFromConfigString(tc.in)
|
||||
if l == nil {
|
||||
t.Errorf("in: %q, failed to create logger from config string", tc.in)
|
||||
continue
|
||||
}
|
||||
ml := l.GetMethodLogger(tc.method)
|
||||
if ml == nil {
|
||||
t.Errorf("in: %q, method logger is nil, want non-nil", tc.in)
|
||||
continue
|
||||
}
|
||||
|
||||
if ml.headerMaxLen != tc.hdr || ml.messageMaxLen != tc.msg {
|
||||
t.Errorf("in: %q, want header: %v, message: %v, got header: %v, message: %v", tc.in, tc.hdr, tc.msg, ml.headerMaxLen, ml.messageMaxLen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// expect method logger to be nil
|
||||
func TestGetMethodLoggerOff(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in string
|
||||
method string
|
||||
}{
|
||||
// method not specified.
|
||||
{
|
||||
in: "s1/m",
|
||||
method: "/s/m",
|
||||
},
|
||||
{
|
||||
in: "s/m1",
|
||||
method: "/s/m",
|
||||
},
|
||||
{
|
||||
in: "s1/*",
|
||||
method: "/s/m",
|
||||
},
|
||||
{
|
||||
in: "s1/*,s/m1",
|
||||
method: "/s/m",
|
||||
},
|
||||
|
||||
// blacklisted.
|
||||
{
|
||||
in: "*,-s/m",
|
||||
method: "/s/m",
|
||||
},
|
||||
{
|
||||
in: "s/*,-s/m",
|
||||
method: "/s/m",
|
||||
},
|
||||
{
|
||||
in: "-s/m,s/*",
|
||||
method: "/s/m",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
l := newLoggerFromConfigString(tc.in)
|
||||
if l == nil {
|
||||
t.Errorf("in: %q, failed to create logger from config string", tc.in)
|
||||
continue
|
||||
}
|
||||
ml := l.GetMethodLogger(tc.method)
|
||||
if ml != nil {
|
||||
t.Errorf("in: %q, method logger is non-nil, want nil", tc.in)
|
||||
}
|
||||
}
|
||||
}
|
206
internal/binarylog/env_config.go
Normal file
206
internal/binarylog/env_config.go
Normal file
@ -0,0 +1,206 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package binarylog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
// newLoggerFromConfigString reads the string and build a logger.
|
||||
//
|
||||
// Example filter config strings:
|
||||
// - "" Nothing will be logged
|
||||
// - "*" All headers and messages will be fully logged.
|
||||
// - "*{h}" Only headers will be logged.
|
||||
// - "*{m:256}" Only the first 256 bytes of each message will be logged.
|
||||
// - "Foo/*" Logs every method in service Foo
|
||||
// - "Foo/*,-Foo/Bar" Logs every method in service Foo except method /Foo/Bar
|
||||
// - "Foo/*,Foo/Bar{m:256}" Logs the first 256 bytes of each message in method
|
||||
// /Foo/Bar, logs all headers and messages in every other method in service
|
||||
// Foo.
|
||||
//
|
||||
// If two configs exist for one certain method or service, the one specified
|
||||
// later overrides the privous config.
|
||||
func newLoggerFromConfigString(s string) *logger {
|
||||
l := newEmptyLogger()
|
||||
methods := strings.Split(s, ",")
|
||||
for _, method := range methods {
|
||||
if err := l.fillMethodLoggerWithConfigString(method); err != nil {
|
||||
grpclog.Warningf("failed to parse binary log config: %v", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// fillMethodLoggerWithConfigString parses config, creates methodLogger and adds
|
||||
// it to the right map in the logger.
|
||||
func (l *logger) fillMethodLoggerWithConfigString(config string) error {
|
||||
// "" is invalid.
|
||||
if config == "" {
|
||||
return errors.New("empty string is not a valid method binary logging config")
|
||||
}
|
||||
|
||||
// "-service/method", blacklist, no * or {} allowed.
|
||||
if config[0] == '-' {
|
||||
s, m, suffix, err := parseMethodConfigAndSuffix(config[1:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid config: %q, %v", config, err)
|
||||
}
|
||||
if m == "*" {
|
||||
return fmt.Errorf("invalid config: %q, %v", config, "* not allowd in blacklist config")
|
||||
}
|
||||
if suffix != "" {
|
||||
return fmt.Errorf("invalid config: %q, %v", config, "header/message limit not allowed in blacklist config")
|
||||
}
|
||||
if err := l.setBlacklist(s + "/" + m); err != nil {
|
||||
return fmt.Errorf("invalid config: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// "*{h:256;m:256}"
|
||||
if config[0] == '*' {
|
||||
hdr, msg, err := parseHeaderMessageLengthConfig(config[1:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid config: %q, %v", config, err)
|
||||
}
|
||||
if err := l.setDefaultMethodLogger(&methodLoggerConfig{hdr: hdr, msg: msg}); err != nil {
|
||||
return fmt.Errorf("invalid config: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
s, m, suffix, err := parseMethodConfigAndSuffix(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid config: %q, %v", config, err)
|
||||
}
|
||||
hdr, msg, err := parseHeaderMessageLengthConfig(suffix)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid header/message length config: %q, %v", suffix, err)
|
||||
}
|
||||
if m == "*" {
|
||||
if err := l.setServiceMethodLogger(s, &methodLoggerConfig{hdr: hdr, msg: msg}); err != nil {
|
||||
return fmt.Errorf("invalid config: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err := l.setMethodMethodLogger(s+"/"+m, &methodLoggerConfig{hdr: hdr, msg: msg}); err != nil {
|
||||
return fmt.Errorf("invalid config: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
// TODO: this const is only used by env_config now. But could be useful for
|
||||
// other config. Move to binarylog.go if necessary.
|
||||
maxUInt = ^uint64(0)
|
||||
|
||||
// For "p.s/m" plus any suffix. Suffix will be parsed again. See test for
|
||||
// expected output.
|
||||
longMethodConfigRegexpStr = `^([\w./]+)/((?:\w+)|[*])(.+)?$`
|
||||
|
||||
// For suffix from above, "{h:123,m:123}". See test for expected output.
|
||||
optionalLengthRegexpStr = `(?::(\d+))?` // Optional ":123".
|
||||
headerConfigRegexpStr = `^{h` + optionalLengthRegexpStr + `}$`
|
||||
messageConfigRegexpStr = `^{m` + optionalLengthRegexpStr + `}$`
|
||||
headerMessageConfigRegexpStr = `^{h` + optionalLengthRegexpStr + `;m` + optionalLengthRegexpStr + `}$`
|
||||
)
|
||||
|
||||
var (
|
||||
longMethodConfigRegexp = regexp.MustCompile(longMethodConfigRegexpStr)
|
||||
headerConfigRegexp = regexp.MustCompile(headerConfigRegexpStr)
|
||||
messageConfigRegexp = regexp.MustCompile(messageConfigRegexpStr)
|
||||
headerMessageConfigRegexp = regexp.MustCompile(headerMessageConfigRegexpStr)
|
||||
)
|
||||
|
||||
// Turn "service/method{h;m}" into "service", "method", "{h;m}".
|
||||
func parseMethodConfigAndSuffix(c string) (service, method, suffix string, _ error) {
|
||||
// Regexp result:
|
||||
//
|
||||
// in: "p.s/m{h:123,m:123}",
|
||||
// out: []string{"p.s/m{h:123,m:123}", "p.s", "m", "{h:123,m:123}"},
|
||||
match := longMethodConfigRegexp.FindStringSubmatch(c)
|
||||
if match == nil {
|
||||
return "", "", "", fmt.Errorf("%q contains invalid substring", c)
|
||||
}
|
||||
service = match[1]
|
||||
method = match[2]
|
||||
suffix = match[3]
|
||||
return
|
||||
}
|
||||
|
||||
// Turn "{h:123;m:345}" into 123, 345.
|
||||
//
|
||||
// Return maxUInt if length is unspecified.
|
||||
func parseHeaderMessageLengthConfig(c string) (hdrLenStr, msgLenStr uint64, err error) {
|
||||
if c == "" {
|
||||
return maxUInt, maxUInt, nil
|
||||
}
|
||||
// Header config only.
|
||||
if match := headerConfigRegexp.FindStringSubmatch(c); match != nil {
|
||||
if s := match[1]; s != "" {
|
||||
hdrLenStr, err = strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("failed to convert %q to uint", s)
|
||||
}
|
||||
return hdrLenStr, 0, nil
|
||||
}
|
||||
return maxUInt, 0, nil
|
||||
}
|
||||
|
||||
// Message config only.
|
||||
if match := messageConfigRegexp.FindStringSubmatch(c); match != nil {
|
||||
if s := match[1]; s != "" {
|
||||
msgLenStr, err = strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("Failed to convert %q to uint", s)
|
||||
}
|
||||
return 0, msgLenStr, nil
|
||||
}
|
||||
return 0, maxUInt, nil
|
||||
}
|
||||
|
||||
// Header and message config both.
|
||||
if match := headerMessageConfigRegexp.FindStringSubmatch(c); match != nil {
|
||||
// Both hdr and msg are specified, but one or two of them might be empty.
|
||||
hdrLenStr = maxUInt
|
||||
msgLenStr = maxUInt
|
||||
if s := match[1]; s != "" {
|
||||
hdrLenStr, err = strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("Failed to convert %q to uint", s)
|
||||
}
|
||||
}
|
||||
if s := match[2]; s != "" {
|
||||
msgLenStr, err = strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("Failed to convert %q to uint", s)
|
||||
}
|
||||
}
|
||||
return hdrLenStr, msgLenStr, nil
|
||||
}
|
||||
return 0, 0, fmt.Errorf("%q contains invalid substring", c)
|
||||
}
|
478
internal/binarylog/env_config_test.go
Normal file
478
internal/binarylog/env_config_test.go
Normal file
@ -0,0 +1,478 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package binarylog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// This tests that when multiple configs are specified, all methods loggers will
|
||||
// be set correctly. Correctness of each logger is covered by other unit tests.
|
||||
func TestNewLoggerFromConfigString(t *testing.T) {
|
||||
const (
|
||||
s1 = "s1"
|
||||
m1 = "m1"
|
||||
m2 = "m2"
|
||||
fullM1 = s1 + "/" + m1
|
||||
fullM2 = s1 + "/" + m2
|
||||
)
|
||||
c := fmt.Sprintf("*{h:1;m:2},%s{h},%s{m},%s{h;m}", s1+"/*", fullM1, fullM2)
|
||||
l := newLoggerFromConfigString(c)
|
||||
|
||||
if l.all.hdr != 1 || l.all.msg != 2 {
|
||||
t.Errorf("l.all = %#v, want headerLen: 1, messageLen: 2", l.all)
|
||||
}
|
||||
|
||||
if ml, ok := l.services[s1]; ok {
|
||||
if ml.hdr != maxUInt || ml.msg != 0 {
|
||||
t.Errorf("want maxUInt header, 0 message, got header: %v, message: %v", ml.hdr, ml.msg)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("service/* is not set")
|
||||
}
|
||||
|
||||
if ml, ok := l.methods[fullM1]; ok {
|
||||
if ml.hdr != 0 || ml.msg != maxUInt {
|
||||
t.Errorf("want 0 header, maxUInt message, got header: %v, message: %v", ml.hdr, ml.msg)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("service/method{h} is not set")
|
||||
}
|
||||
|
||||
if ml, ok := l.methods[fullM2]; ok {
|
||||
if ml.hdr != maxUInt || ml.msg != maxUInt {
|
||||
t.Errorf("want maxUInt header, maxUInt message, got header: %v, message: %v", ml.hdr, ml.msg)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("service/method{h;m} is not set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewLoggerFromConfigStringInvalid(t *testing.T) {
|
||||
testCases := []string{
|
||||
"",
|
||||
"*{}",
|
||||
"s/m,*{}",
|
||||
"s/m,s/m{a}",
|
||||
|
||||
// Duplciate rules.
|
||||
"s/m,-s/m",
|
||||
"-s/m,s/m",
|
||||
"s/m,s/m",
|
||||
"s/m,s/m{h:1;m:1}",
|
||||
"s/m{h:1;m:1},s/m",
|
||||
"-s/m,-s/m",
|
||||
"s/*,s/*{h:1;m:1}",
|
||||
"*,*{h:1;m:1}",
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
l := newLoggerFromConfigString(tc)
|
||||
if l != nil {
|
||||
t.Errorf("With config %q, want logger %v, got %v", tc, nil, l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMethodConfigAndSuffix(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in, service, method, suffix string
|
||||
}{
|
||||
{
|
||||
in: "p.s/m",
|
||||
service: "p.s", method: "m", suffix: "",
|
||||
},
|
||||
{
|
||||
in: "p.s/m{h,m}",
|
||||
service: "p.s", method: "m", suffix: "{h,m}",
|
||||
},
|
||||
{
|
||||
in: "p.s/*",
|
||||
service: "p.s", method: "*", suffix: "",
|
||||
},
|
||||
{
|
||||
in: "p.s/*{h,m}",
|
||||
service: "p.s", method: "*", suffix: "{h,m}",
|
||||
},
|
||||
|
||||
// invalid suffix will be detected by another function.
|
||||
{
|
||||
in: "p.s/m{invalidsuffix}",
|
||||
service: "p.s", method: "m", suffix: "{invalidsuffix}",
|
||||
},
|
||||
{
|
||||
in: "p.s/*{invalidsuffix}",
|
||||
service: "p.s", method: "*", suffix: "{invalidsuffix}",
|
||||
},
|
||||
{
|
||||
in: "s/m*",
|
||||
service: "s", method: "m", suffix: "*",
|
||||
},
|
||||
{
|
||||
in: "s/*m",
|
||||
service: "s", method: "*", suffix: "m",
|
||||
},
|
||||
{
|
||||
in: "s/**",
|
||||
service: "s", method: "*", suffix: "*",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Logf("testing parseMethodConfigAndSuffix(%q)", tc.in)
|
||||
s, m, suffix, err := parseMethodConfigAndSuffix(tc.in)
|
||||
if err != nil {
|
||||
t.Errorf("returned error %v, want nil", err)
|
||||
continue
|
||||
}
|
||||
if s != tc.service {
|
||||
t.Errorf("service = %q, want %q", s, tc.service)
|
||||
}
|
||||
if m != tc.method {
|
||||
t.Errorf("method = %q, want %q", m, tc.method)
|
||||
}
|
||||
if suffix != tc.suffix {
|
||||
t.Errorf("suffix = %q, want %q", suffix, tc.suffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMethodConfigAndSuffixInvalid(t *testing.T) {
|
||||
testCases := []string{
|
||||
"*/m",
|
||||
"*/m{}",
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
s, m, suffix, err := parseMethodConfigAndSuffix(tc)
|
||||
if err == nil {
|
||||
t.Errorf("Parsing %q got nil error with %q, %q, %q, want non-nil error", tc, s, m, suffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseHeaderMessageLengthConfig(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in string
|
||||
hdr, msg uint64
|
||||
}{
|
||||
{
|
||||
in: "",
|
||||
hdr: maxUInt, msg: maxUInt,
|
||||
},
|
||||
{
|
||||
in: "{h}",
|
||||
hdr: maxUInt, msg: 0,
|
||||
},
|
||||
{
|
||||
in: "{h:314}",
|
||||
hdr: 314, msg: 0,
|
||||
},
|
||||
{
|
||||
in: "{m}",
|
||||
hdr: 0, msg: maxUInt,
|
||||
},
|
||||
{
|
||||
in: "{m:213}",
|
||||
hdr: 0, msg: 213,
|
||||
},
|
||||
{
|
||||
in: "{h;m}",
|
||||
hdr: maxUInt, msg: maxUInt,
|
||||
},
|
||||
{
|
||||
in: "{h:314;m}",
|
||||
hdr: 314, msg: maxUInt,
|
||||
},
|
||||
{
|
||||
in: "{h;m:213}",
|
||||
hdr: maxUInt, msg: 213,
|
||||
},
|
||||
{
|
||||
in: "{h:314;m:213}",
|
||||
hdr: 314, msg: 213,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Logf("testing parseHeaderMessageLengthConfig(%q)", tc.in)
|
||||
hdr, msg, err := parseHeaderMessageLengthConfig(tc.in)
|
||||
if err != nil {
|
||||
t.Errorf("returned error %v, want nil", err)
|
||||
continue
|
||||
}
|
||||
if hdr != tc.hdr {
|
||||
t.Errorf("header length = %v, want %v", hdr, tc.hdr)
|
||||
}
|
||||
if msg != tc.msg {
|
||||
t.Errorf("message length = %v, want %v", msg, tc.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
func TestParseHeaderMessageLengthConfigInvalid(t *testing.T) {
|
||||
testCases := []string{
|
||||
"{}",
|
||||
"{h;a}",
|
||||
"{h;m;b}",
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
_, _, err := parseHeaderMessageLengthConfig(tc)
|
||||
if err == nil {
|
||||
t.Errorf("Parsing %q got nil error, want non-nil error", tc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFillMethodLoggerWithConfigStringBlacklist(t *testing.T) {
|
||||
testCases := []string{
|
||||
"p.s/m",
|
||||
"service/method",
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
c := "-" + tc
|
||||
t.Logf("testing fillMethodLoggerWithConfigString(%q)", c)
|
||||
l := newEmptyLogger()
|
||||
if err := l.fillMethodLoggerWithConfigString(c); err != nil {
|
||||
t.Errorf("returned err %v, want nil", err)
|
||||
continue
|
||||
}
|
||||
_, ok := l.blacklist[tc]
|
||||
if !ok {
|
||||
t.Errorf("blacklist[%q] is not set", tc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFillMethodLoggerWithConfigStringGlobal(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in string
|
||||
hdr, msg uint64
|
||||
}{
|
||||
{
|
||||
in: "",
|
||||
hdr: maxUInt, msg: maxUInt,
|
||||
},
|
||||
{
|
||||
in: "{h}",
|
||||
hdr: maxUInt, msg: 0,
|
||||
},
|
||||
{
|
||||
in: "{h:314}",
|
||||
hdr: 314, msg: 0,
|
||||
},
|
||||
{
|
||||
in: "{m}",
|
||||
hdr: 0, msg: maxUInt,
|
||||
},
|
||||
{
|
||||
in: "{m:213}",
|
||||
hdr: 0, msg: 213,
|
||||
},
|
||||
{
|
||||
in: "{h;m}",
|
||||
hdr: maxUInt, msg: maxUInt,
|
||||
},
|
||||
{
|
||||
in: "{h:314;m}",
|
||||
hdr: 314, msg: maxUInt,
|
||||
},
|
||||
{
|
||||
in: "{h;m:213}",
|
||||
hdr: maxUInt, msg: 213,
|
||||
},
|
||||
{
|
||||
in: "{h:314;m:213}",
|
||||
hdr: 314, msg: 213,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
c := "*" + tc.in
|
||||
t.Logf("testing fillMethodLoggerWithConfigString(%q)", c)
|
||||
l := newEmptyLogger()
|
||||
if err := l.fillMethodLoggerWithConfigString(c); err != nil {
|
||||
t.Errorf("returned err %v, want nil", err)
|
||||
continue
|
||||
}
|
||||
if l.all == nil {
|
||||
t.Errorf("l.all is not set")
|
||||
continue
|
||||
}
|
||||
if hdr := l.all.hdr; hdr != tc.hdr {
|
||||
t.Errorf("header length = %v, want %v", hdr, tc.hdr)
|
||||
|
||||
}
|
||||
if msg := l.all.msg; msg != tc.msg {
|
||||
t.Errorf("message length = %v, want %v", msg, tc.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFillMethodLoggerWithConfigStringPerService(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in string
|
||||
hdr, msg uint64
|
||||
}{
|
||||
{
|
||||
in: "",
|
||||
hdr: maxUInt, msg: maxUInt,
|
||||
},
|
||||
{
|
||||
in: "{h}",
|
||||
hdr: maxUInt, msg: 0,
|
||||
},
|
||||
{
|
||||
in: "{h:314}",
|
||||
hdr: 314, msg: 0,
|
||||
},
|
||||
{
|
||||
in: "{m}",
|
||||
hdr: 0, msg: maxUInt,
|
||||
},
|
||||
{
|
||||
in: "{m:213}",
|
||||
hdr: 0, msg: 213,
|
||||
},
|
||||
{
|
||||
in: "{h;m}",
|
||||
hdr: maxUInt, msg: maxUInt,
|
||||
},
|
||||
{
|
||||
in: "{h:314;m}",
|
||||
hdr: 314, msg: maxUInt,
|
||||
},
|
||||
{
|
||||
in: "{h;m:213}",
|
||||
hdr: maxUInt, msg: 213,
|
||||
},
|
||||
{
|
||||
in: "{h:314;m:213}",
|
||||
hdr: 314, msg: 213,
|
||||
},
|
||||
}
|
||||
const serviceName = "service"
|
||||
for _, tc := range testCases {
|
||||
c := serviceName + "/*" + tc.in
|
||||
t.Logf("testing fillMethodLoggerWithConfigString(%q)", c)
|
||||
l := newEmptyLogger()
|
||||
if err := l.fillMethodLoggerWithConfigString(c); err != nil {
|
||||
t.Errorf("returned err %v, want nil", err)
|
||||
continue
|
||||
}
|
||||
ml, ok := l.services[serviceName]
|
||||
if !ok {
|
||||
t.Errorf("l.service[%q] is not set", serviceName)
|
||||
continue
|
||||
}
|
||||
if hdr := ml.hdr; hdr != tc.hdr {
|
||||
t.Errorf("header length = %v, want %v", hdr, tc.hdr)
|
||||
|
||||
}
|
||||
if msg := ml.msg; msg != tc.msg {
|
||||
t.Errorf("message length = %v, want %v", msg, tc.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFillMethodLoggerWithConfigStringPerMethod(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in string
|
||||
hdr, msg uint64
|
||||
}{
|
||||
{
|
||||
in: "",
|
||||
hdr: maxUInt, msg: maxUInt,
|
||||
},
|
||||
{
|
||||
in: "{h}",
|
||||
hdr: maxUInt, msg: 0,
|
||||
},
|
||||
{
|
||||
in: "{h:314}",
|
||||
hdr: 314, msg: 0,
|
||||
},
|
||||
{
|
||||
in: "{m}",
|
||||
hdr: 0, msg: maxUInt,
|
||||
},
|
||||
{
|
||||
in: "{m:213}",
|
||||
hdr: 0, msg: 213,
|
||||
},
|
||||
{
|
||||
in: "{h;m}",
|
||||
hdr: maxUInt, msg: maxUInt,
|
||||
},
|
||||
{
|
||||
in: "{h:314;m}",
|
||||
hdr: 314, msg: maxUInt,
|
||||
},
|
||||
{
|
||||
in: "{h;m:213}",
|
||||
hdr: maxUInt, msg: 213,
|
||||
},
|
||||
{
|
||||
in: "{h:314;m:213}",
|
||||
hdr: 314, msg: 213,
|
||||
},
|
||||
}
|
||||
const (
|
||||
serviceName = "service"
|
||||
methodName = "method"
|
||||
fullMethodName = serviceName + "/" + methodName
|
||||
)
|
||||
for _, tc := range testCases {
|
||||
c := fullMethodName + tc.in
|
||||
t.Logf("testing fillMethodLoggerWithConfigString(%q)", c)
|
||||
l := newEmptyLogger()
|
||||
if err := l.fillMethodLoggerWithConfigString(c); err != nil {
|
||||
t.Errorf("returned err %v, want nil", err)
|
||||
continue
|
||||
}
|
||||
ml, ok := l.methods[fullMethodName]
|
||||
if !ok {
|
||||
t.Errorf("l.methods[%q] is not set", fullMethodName)
|
||||
continue
|
||||
}
|
||||
if hdr := ml.hdr; hdr != tc.hdr {
|
||||
t.Errorf("header length = %v, want %v", hdr, tc.hdr)
|
||||
|
||||
}
|
||||
if msg := ml.msg; msg != tc.msg {
|
||||
t.Errorf("message length = %v, want %v", msg, tc.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFillMethodLoggerWithConfigStringInvalid(t *testing.T) {
|
||||
testCases := []string{
|
||||
"",
|
||||
"{}",
|
||||
"p.s/m{}",
|
||||
"p.s/m{a}",
|
||||
"p.s/m*",
|
||||
"p.s/**",
|
||||
"*/m",
|
||||
|
||||
"-p.s/*",
|
||||
"-p.s/m{h}",
|
||||
}
|
||||
l := &logger{}
|
||||
for _, tc := range testCases {
|
||||
if err := l.fillMethodLoggerWithConfigString(tc); err == nil {
|
||||
t.Errorf("fillMethodLoggerWithConfigString(%q) returned nil error, want non-nil", tc)
|
||||
}
|
||||
}
|
||||
}
|
39
internal/binarylog/method_logger.go
Normal file
39
internal/binarylog/method_logger.go
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package binarylog
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// MethodLogger is the sub-logger for each method.
|
||||
type MethodLogger struct {
|
||||
headerMaxLen, messageMaxLen uint64
|
||||
|
||||
sink io.Writer // TODO(blog): make this plugable.
|
||||
}
|
||||
|
||||
func newMethodLogger(h, m uint64) *MethodLogger {
|
||||
return &MethodLogger{
|
||||
headerMaxLen: h,
|
||||
messageMaxLen: m,
|
||||
|
||||
sink: nil, // TODO(blog): make it plugable.
|
||||
}
|
||||
}
|
179
internal/binarylog/regexp_test.go
Normal file
179
internal/binarylog/regexp_test.go
Normal file
@ -0,0 +1,179 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package binarylog
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLongMethodConfigRegexp(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in string
|
||||
out []string
|
||||
}{
|
||||
{in: "", out: nil},
|
||||
{in: "*/m", out: nil},
|
||||
|
||||
{
|
||||
in: "p.s/m{}",
|
||||
out: []string{"p.s/m{}", "p.s", "m", "{}"},
|
||||
},
|
||||
|
||||
{
|
||||
in: "p.s/m",
|
||||
out: []string{"p.s/m", "p.s", "m", ""},
|
||||
},
|
||||
{
|
||||
in: "p.s/m{h}",
|
||||
out: []string{"p.s/m{h}", "p.s", "m", "{h}"},
|
||||
},
|
||||
{
|
||||
in: "p.s/m{m}",
|
||||
out: []string{"p.s/m{m}", "p.s", "m", "{m}"},
|
||||
},
|
||||
{
|
||||
in: "p.s/m{h:123}",
|
||||
out: []string{"p.s/m{h:123}", "p.s", "m", "{h:123}"},
|
||||
},
|
||||
{
|
||||
in: "p.s/m{m:123}",
|
||||
out: []string{"p.s/m{m:123}", "p.s", "m", "{m:123}"},
|
||||
},
|
||||
{
|
||||
in: "p.s/m{h:123,m:123}",
|
||||
out: []string{"p.s/m{h:123,m:123}", "p.s", "m", "{h:123,m:123}"},
|
||||
},
|
||||
|
||||
{
|
||||
in: "p.s/*",
|
||||
out: []string{"p.s/*", "p.s", "*", ""},
|
||||
},
|
||||
{
|
||||
in: "p.s/*{h}",
|
||||
out: []string{"p.s/*{h}", "p.s", "*", "{h}"},
|
||||
},
|
||||
|
||||
{
|
||||
in: "s/m*",
|
||||
out: []string{"s/m*", "s", "m", "*"},
|
||||
},
|
||||
{
|
||||
in: "s/**",
|
||||
out: []string{"s/**", "s", "*", "*"},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
match := longMethodConfigRegexp.FindStringSubmatch(tc.in)
|
||||
if !reflect.DeepEqual(match, tc.out) {
|
||||
t.Errorf("in: %q, out: %q, want: %q", tc.in, match, tc.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeaderConfigRegexp(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in string
|
||||
out []string
|
||||
}{
|
||||
{in: "{}", out: nil},
|
||||
{in: "{a:b}", out: nil},
|
||||
{in: "{m:123}", out: nil},
|
||||
{in: "{h:123;m:123}", out: nil},
|
||||
|
||||
{
|
||||
in: "{h}",
|
||||
out: []string{"{h}", ""},
|
||||
},
|
||||
{
|
||||
in: "{h:123}",
|
||||
out: []string{"{h:123}", "123"},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
match := headerConfigRegexp.FindStringSubmatch(tc.in)
|
||||
if !reflect.DeepEqual(match, tc.out) {
|
||||
t.Errorf("in: %q, out: %q, want: %q", tc.in, match, tc.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessageConfigRegexp(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in string
|
||||
out []string
|
||||
}{
|
||||
{in: "{}", out: nil},
|
||||
{in: "{a:b}", out: nil},
|
||||
{in: "{h:123}", out: nil},
|
||||
{in: "{h:123;m:123}", out: nil},
|
||||
|
||||
{
|
||||
in: "{m}",
|
||||
out: []string{"{m}", ""},
|
||||
},
|
||||
{
|
||||
in: "{m:123}",
|
||||
out: []string{"{m:123}", "123"},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
match := messageConfigRegexp.FindStringSubmatch(tc.in)
|
||||
if !reflect.DeepEqual(match, tc.out) {
|
||||
t.Errorf("in: %q, out: %q, want: %q", tc.in, match, tc.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeaderMessageConfigRegexp(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in string
|
||||
out []string
|
||||
}{
|
||||
{in: "{}", out: nil},
|
||||
{in: "{a:b}", out: nil},
|
||||
{in: "{h}", out: nil},
|
||||
{in: "{h:123}", out: nil},
|
||||
{in: "{m}", out: nil},
|
||||
{in: "{m:123}", out: nil},
|
||||
|
||||
{
|
||||
in: "{h;m}",
|
||||
out: []string{"{h;m}", "", ""},
|
||||
},
|
||||
{
|
||||
in: "{h:123;m}",
|
||||
out: []string{"{h:123;m}", "123", ""},
|
||||
},
|
||||
{
|
||||
in: "{h;m:123}",
|
||||
out: []string{"{h;m:123}", "", "123"},
|
||||
},
|
||||
{
|
||||
in: "{h:123;m:123}",
|
||||
out: []string{"{h:123;m:123}", "123", "123"},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
match := headerMessageConfigRegexp.FindStringSubmatch(tc.in)
|
||||
if !reflect.DeepEqual(match, tc.out) {
|
||||
t.Errorf("in: %q, out: %q, want: %q", tc.in, match, tc.out)
|
||||
}
|
||||
}
|
||||
}
|
41
internal/binarylog/util.go
Normal file
41
internal/binarylog/util.go
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package binarylog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// parseMethodName splits service and method from the input. It expects format
|
||||
// "/service/method".
|
||||
//
|
||||
// TODO: move to internal/grpcutil.
|
||||
func parseMethodName(methodName string) (service, method string, _ error) {
|
||||
if !strings.HasPrefix(methodName, "/") {
|
||||
return "", "", errors.New("invalid method name: should start with /")
|
||||
}
|
||||
methodName = methodName[1:]
|
||||
|
||||
pos := strings.LastIndex(methodName, "/")
|
||||
if pos < 0 {
|
||||
return "", "", errors.New("invalid method name: suffix /method is missing")
|
||||
}
|
||||
return methodName[:pos], methodName[pos+1:], nil
|
||||
}
|
59
internal/binarylog/util_test.go
Normal file
59
internal/binarylog/util_test.go
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package binarylog
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseMethodName(t *testing.T) {
|
||||
testCases := []struct {
|
||||
methodName string
|
||||
service, method string
|
||||
}{
|
||||
{methodName: "/s/m", service: "s", method: "m"},
|
||||
{methodName: "/p.s/m", service: "p.s", method: "m"},
|
||||
{methodName: "/p/s/m", service: "p/s", method: "m"},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
s, m, err := parseMethodName(tc.methodName)
|
||||
if err != nil {
|
||||
t.Errorf("Parsing %q got error %v, want nil", tc.methodName, err)
|
||||
continue
|
||||
}
|
||||
if s != tc.service || m != tc.method {
|
||||
t.Errorf("Parseing %q got service %q, method %q, want service %q, method %q",
|
||||
tc.methodName, s, m, tc.service, tc.method,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMethodNameInvalid(t *testing.T) {
|
||||
testCases := []string{
|
||||
"/",
|
||||
"/sm",
|
||||
"",
|
||||
"sm",
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
_, _, err := parseMethodName(tc)
|
||||
if err == nil {
|
||||
t.Errorf("Parsing %q got nil error, want non-nil error", tc)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user