blob: 45ec5babc87b2a59b8c7b531fab06af86fa7fb10 [file] [log] [blame]
Carmelo Cascone73f45302019-02-04 23:11:26 -08001/*
2 * Copyright 2019-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.grpc.ctl;
18
19import io.grpc.CallOptions;
20import io.grpc.Channel;
21import io.grpc.ClientCall;
22import io.grpc.ClientInterceptor;
23import io.grpc.ForwardingClientCall;
24import io.grpc.ForwardingClientCallListener;
25import io.grpc.Metadata;
26import io.grpc.MethodDescriptor;
27import io.grpc.Status;
28import io.grpc.protobuf.lite.ProtoLiteUtils;
29import org.onosproject.grpc.api.GrpcChannelId;
30import org.slf4j.Logger;
31
32import java.io.File;
33import java.io.FileWriter;
34import java.io.IOException;
35import java.text.SimpleDateFormat;
36import java.util.Date;
37import java.util.StringJoiner;
38import java.util.concurrent.atomic.AtomicBoolean;
39import java.util.concurrent.atomic.AtomicLong;
40
41import static java.lang.String.format;
42import static org.slf4j.LoggerFactory.getLogger;
43
44/**
45 * gRPC client interceptor that logs to file all messages sent and received.
46 */
47final class GrpcLoggingInterceptor implements ClientInterceptor {
48
49 private static final Metadata.Key<com.google.rpc.Status> GRPC_STATUS_KEY =
50 Metadata.Key.of(
51 "grpc-status-details-bin",
52 ProtoLiteUtils.metadataMarshaller(
53 com.google.rpc.Status.getDefaultInstance()));
54
55 private static final Logger log = getLogger(GrpcLoggingInterceptor.class);
56
57 private final AtomicLong callIdGenerator = new AtomicLong();
58 private final GrpcChannelId channelId;
59 private final AtomicBoolean enabled;
60
61 private FileWriter writer;
62
63 GrpcLoggingInterceptor(GrpcChannelId channelId, AtomicBoolean enabled) {
64 this.channelId = channelId;
65 this.enabled = enabled;
Carmelo Cascone73f45302019-02-04 23:11:26 -080066 }
67
Carmelo Cascone62d5c2e2019-03-07 18:53:17 -080068 private boolean initWriter() {
69 if (writer != null) {
70 return true;
71 }
Carmelo Cascone73f45302019-02-04 23:11:26 -080072 final String safeChName = channelId.id()
73 .replaceAll("[^A-Za-z0-9]", "_");
74 final String fileName = format("grpc_%s_", safeChName).toLowerCase();
Carmelo Cascone62d5c2e2019-03-07 18:53:17 -080075 try {
76 final File tmpFile = File.createTempFile(fileName, ".log");
77 this.writer = new FileWriter(tmpFile);
78 log.info("Created gRPC call log file for channel {}: {}",
79 channelId, tmpFile.getAbsolutePath());
80 return true;
81 } catch (IOException e) {
82 log.error("Unable to initialize gRPC call log writer", e);
83 }
84 return false;
Carmelo Cascone73f45302019-02-04 23:11:26 -080085 }
86
87 void close() {
88 synchronized (this) {
89 if (writer == null) {
90 return;
91 }
92 try {
93 log.info("Closing log writer for {}...", channelId);
94 writer.close();
95 } catch (IOException e) {
96 log.error("Unable to close gRPC call log writer", e);
97 }
98 writer = null;
99 }
100 }
101
102 private void write(String message) {
103 synchronized (this) {
Carmelo Cascone62d5c2e2019-03-07 18:53:17 -0800104 if (!initWriter()) {
105 return;
106 }
107 if (message.length() > 4096) {
108 message = message.substring(0, 256) + "... TRUNCATED!\n\n";
109 }
110 try {
111 writer.write(format(
112 "*** %s - %s",
113 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S")
114 .format(new Date()),
115 message));
116 } catch (IOException e) {
117 log.error("Unable to write gRPC call log", e);
Carmelo Cascone73f45302019-02-04 23:11:26 -0800118 }
119 }
120 }
121
122 @Override
123 public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
124 MethodDescriptor<ReqT, RespT> methodDescriptor,
125 CallOptions callOptions, Channel channel) {
126 return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
127 channel.newCall(methodDescriptor, callOptions.withoutWaitForReady())) {
128
129 private final long callId = callIdGenerator.getAndIncrement();
130
131 @Override
132 public void sendMessage(ReqT message) {
133 if (enabled.get()) {
134 write(format(
135 "%s >> OUTBOUND >> [callId=%s]\n%s\n",
136 methodDescriptor.getFullMethodName(),
137 callId,
138 message.toString()));
139 }
140 super.sendMessage(message);
141 }
142
143 @Override
144 public void start(Listener<RespT> responseListener, Metadata headers) {
145
146 if (enabled.get()) {
147 write(format(
148 "%s STARTED [callId=%s]\n%s\n\n",
149 methodDescriptor.getFullMethodName(),
150 callId,
151 headers.toString()));
152 }
153
154 Listener<RespT> listener = new ForwardingClientCallListener<RespT>() {
155 @Override
156 protected Listener<RespT> delegate() {
157 return responseListener;
158 }
159
160 @Override
161 public void onMessage(RespT message) {
162 if (enabled.get()) {
163 write(format(
164 "%s << INBOUND << [callId=%s]\n%s\n",
165 methodDescriptor.getFullMethodName(),
166 callId,
167 message.toString()));
168 }
169 super.onMessage(message);
170 }
171
172 @Override
173 public void onClose(Status status, Metadata trailers) {
174 if (enabled.get()) {
175 write(format(
176 "%s CLOSED [callId=%s]\n%s\n%s\n\n",
177 methodDescriptor.getFullMethodName(),
178 callId,
179 status.toString(),
180 parseTrailers(trailers)));
181 }
182 super.onClose(status, trailers);
183 }
184
185 private String parseTrailers(Metadata trailers) {
186 StringJoiner joiner = new StringJoiner("\n");
187 joiner.add(trailers.toString());
188 // If Google's RPC Status trailers are found, parse them.
189 final Iterable<com.google.rpc.Status> statuses = trailers.getAll(
190 GRPC_STATUS_KEY);
191 if (statuses == null) {
192 return joiner.toString();
193 }
194 statuses.forEach(s -> joiner.add(s.toString()));
195 return joiner.toString();
196 }
197
198 @Override
199 public void onHeaders(Metadata headers) {
200 super.onHeaders(headers);
201 }
202 };
203
204 super.start(listener, headers);
205 }
206 };
207 }
208}