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)
29
30// Logger is the global binary logger. It can be used to get binary logger for
31// each method.
32type Logger interface {
33	getMethodLogger(methodName string) *MethodLogger
34}
35
36// binLogger is the global binary logger for the binary. One of this should be
37// built at init time from the configuration (environment variable or flags).
38//
39// It is used to get a methodLogger for each individual method.
40var binLogger Logger
41
42// SetLogger sets the binarg logger.
43//
44// Only call this at init time.
45func SetLogger(l Logger) {
46	binLogger = l
47}
48
49// GetMethodLogger returns the methodLogger for the given methodName.
50//
51// methodName should be in the format of "/service/method".
52//
53// Each methodLogger returned by this method is a new instance. This is to
54// generate sequence id within the call.
55func GetMethodLogger(methodName string) *MethodLogger {
56	if binLogger == nil {
57		return nil
58	}
59	return binLogger.getMethodLogger(methodName)
60}
61
62func init() {
63	const envStr = "GRPC_BINARY_LOG_FILTER"
64	configStr := os.Getenv(envStr)
65	binLogger = NewLoggerFromConfigString(configStr)
66}
67
68type methodLoggerConfig struct {
69	// Max length of header and message.
70	hdr, msg uint64
71}
72
73type logger struct {
74	all      *methodLoggerConfig
75	services map[string]*methodLoggerConfig
76	methods  map[string]*methodLoggerConfig
77
78	blacklist map[string]struct{}
79}
80
81// newEmptyLogger creates an empty logger. The map fields need to be filled in
82// using the set* functions.
83func newEmptyLogger() *logger {
84	return &logger{}
85}
86
87// Set method logger for "*".
88func (l *logger) setDefaultMethodLogger(ml *methodLoggerConfig) error {
89	if l.all != nil {
90		return fmt.Errorf("conflicting global rules found")
91	}
92	l.all = ml
93	return nil
94}
95
96// Set method logger for "service/*".
97//
98// New methodLogger with same service overrides the old one.
99func (l *logger) setServiceMethodLogger(service string, ml *methodLoggerConfig) error {
100	if _, ok := l.services[service]; ok {
101		return fmt.Errorf("conflicting service rules for service %v found", service)
102	}
103	if l.services == nil {
104		l.services = make(map[string]*methodLoggerConfig)
105	}
106	l.services[service] = ml
107	return nil
108}
109
110// Set method logger for "service/method".
111//
112// New methodLogger with same method overrides the old one.
113func (l *logger) setMethodMethodLogger(method string, ml *methodLoggerConfig) error {
114	if _, ok := l.blacklist[method]; ok {
115		return fmt.Errorf("conflicting blacklist rules for method %v found", method)
116	}
117	if _, ok := l.methods[method]; ok {
118		return fmt.Errorf("conflicting method rules for method %v found", method)
119	}
120	if l.methods == nil {
121		l.methods = make(map[string]*methodLoggerConfig)
122	}
123	l.methods[method] = ml
124	return nil
125}
126
127// Set blacklist method for "-service/method".
128func (l *logger) setBlacklist(method string) error {
129	if _, ok := l.blacklist[method]; ok {
130		return fmt.Errorf("conflicting blacklist rules for method %v found", method)
131	}
132	if _, ok := l.methods[method]; ok {
133		return fmt.Errorf("conflicting method rules for method %v found", method)
134	}
135	if l.blacklist == nil {
136		l.blacklist = make(map[string]struct{})
137	}
138	l.blacklist[method] = struct{}{}
139	return nil
140}
141
142// getMethodLogger returns the methodLogger for the given methodName.
143//
144// methodName should be in the format of "/service/method".
145//
146// Each methodLogger returned by this method is a new instance. This is to
147// generate sequence id within the call.
148func (l *logger) getMethodLogger(methodName string) *MethodLogger {
149	s, m, err := parseMethodName(methodName)
150	if err != nil {
151		grpclog.Infof("binarylogging: failed to parse %q: %v", methodName, err)
152		return nil
153	}
154	if ml, ok := l.methods[s+"/"+m]; ok {
155		return newMethodLogger(ml.hdr, ml.msg)
156	}
157	if _, ok := l.blacklist[s+"/"+m]; ok {
158		return nil
159	}
160	if ml, ok := l.services[s]; ok {
161		return newMethodLogger(ml.hdr, ml.msg)
162	}
163	if l.all == nil {
164		return nil
165	}
166	return newMethodLogger(l.all.hdr, l.all.msg)
167}
168