1/*
2 *
3 * Copyright 2018 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19// Package binarylog implementation binary logging as defined in
20// https://github.com/grpc/proposal/blob/master/A16-binary-logging.md.
21package binarylog
22
23import (
24	"fmt"
25	"os"
26
27	"google.golang.org/grpc/grpclog"
28	"google.golang.org/grpc/internal/grpcutil"
29)
30
31// Logger is the global binary logger. It can be used to get binary logger for
32// each method.
33type Logger interface {
34	getMethodLogger(methodName string) *MethodLogger
35}
36
37// binLogger is the global binary logger for the binary. One of this should be
38// built at init time from the configuration (environment variable or flags).
39//
40// It is used to get a methodLogger for each individual method.
41var binLogger Logger
42
43// SetLogger sets the binarg logger.
44//
45// Only call this at init time.
46func SetLogger(l Logger) {
47	binLogger = l
48}
49
50// GetMethodLogger returns the methodLogger for the given methodName.
51//
52// methodName should be in the format of "/service/method".
53//
54// Each methodLogger returned by this method is a new instance. This is to
55// generate sequence id within the call.
56func GetMethodLogger(methodName string) *MethodLogger {
57	if binLogger == nil {
58		return nil
59	}
60	return binLogger.getMethodLogger(methodName)
61}
62
63func init() {
64	const envStr = "GRPC_BINARY_LOG_FILTER"
65	configStr := os.Getenv(envStr)
66	binLogger = NewLoggerFromConfigString(configStr)
67}
68
69type methodLoggerConfig struct {
70	// Max length of header and message.
71	hdr, msg uint64
72}
73
74type logger struct {
75	all      *methodLoggerConfig
76	services map[string]*methodLoggerConfig
77	methods  map[string]*methodLoggerConfig
78
79	blacklist map[string]struct{}
80}
81
82// newEmptyLogger creates an empty logger. The map fields need to be filled in
83// using the set* functions.
84func newEmptyLogger() *logger {
85	return &logger{}
86}
87
88// Set method logger for "*".
89func (l *logger) setDefaultMethodLogger(ml *methodLoggerConfig) error {
90	if l.all != nil {
91		return fmt.Errorf("conflicting global rules found")
92	}
93	l.all = ml
94	return nil
95}
96
97// Set method logger for "service/*".
98//
99// New methodLogger with same service overrides the old one.
100func (l *logger) setServiceMethodLogger(service string, ml *methodLoggerConfig) error {
101	if _, ok := l.services[service]; ok {
102		return fmt.Errorf("conflicting service rules for service %v found", service)
103	}
104	if l.services == nil {
105		l.services = make(map[string]*methodLoggerConfig)
106	}
107	l.services[service] = ml
108	return nil
109}
110
111// Set method logger for "service/method".
112//
113// New methodLogger with same method overrides the old one.
114func (l *logger) setMethodMethodLogger(method string, ml *methodLoggerConfig) error {
115	if _, ok := l.blacklist[method]; ok {
116		return fmt.Errorf("conflicting blacklist rules for method %v found", method)
117	}
118	if _, ok := l.methods[method]; ok {
119		return fmt.Errorf("conflicting method rules for method %v found", method)
120	}
121	if l.methods == nil {
122		l.methods = make(map[string]*methodLoggerConfig)
123	}
124	l.methods[method] = ml
125	return nil
126}
127
128// Set blacklist method for "-service/method".
129func (l *logger) setBlacklist(method string) error {
130	if _, ok := l.blacklist[method]; ok {
131		return fmt.Errorf("conflicting blacklist rules for method %v found", method)
132	}
133	if _, ok := l.methods[method]; ok {
134		return fmt.Errorf("conflicting method rules for method %v found", method)
135	}
136	if l.blacklist == nil {
137		l.blacklist = make(map[string]struct{})
138	}
139	l.blacklist[method] = struct{}{}
140	return nil
141}
142
143// getMethodLogger returns the methodLogger for the given methodName.
144//
145// methodName should be in the format of "/service/method".
146//
147// Each methodLogger returned by this method is a new instance. This is to
148// generate sequence id within the call.
149func (l *logger) getMethodLogger(methodName string) *MethodLogger {
150	s, m, err := grpcutil.ParseMethod(methodName)
151	if err != nil {
152		grpclog.Infof("binarylogging: failed to parse %q: %v", methodName, err)
153		return nil
154	}
155	if ml, ok := l.methods[s+"/"+m]; ok {
156		return newMethodLogger(ml.hdr, ml.msg)
157	}
158	if _, ok := l.blacklist[s+"/"+m]; ok {
159		return nil
160	}
161	if ml, ok := l.services[s]; ok {
162		return newMethodLogger(ml.hdr, ml.msg)
163	}
164	if l.all == nil {
165		return nil
166	}
167	return newMethodLogger(l.all.hdr, l.all.msg)
168}
169