blob: 774152e5fb0fb61cb67c05ddfe2759817c57b69d [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;
66 try {
67 writer = initWriter();
68 write(format("GRPC CALL LOG - %s\n\n", channelId));
69 } catch (IOException e) {
70 log.error("Unable to initialize gRPC call log writer", e);
71 }
72 }
73
74 private FileWriter initWriter() throws IOException {
75 final String safeChName = channelId.id()
76 .replaceAll("[^A-Za-z0-9]", "_");
77 final String fileName = format("grpc_%s_", safeChName).toLowerCase();
78 final File tmpFile = File.createTempFile(fileName, ".log");
79 log.info("Created gRPC call log file for channel {}: {}",
80 channelId, tmpFile.getAbsolutePath());
81 return new FileWriter(tmpFile);
82 }
83
84 void close() {
85 synchronized (this) {
86 if (writer == null) {
87 return;
88 }
89 try {
90 log.info("Closing log writer for {}...", channelId);
91 writer.close();
92 } catch (IOException e) {
93 log.error("Unable to close gRPC call log writer", e);
94 }
95 writer = null;
96 }
97 }
98
99 private void write(String message) {
100 synchronized (this) {
101 if (writer != null) {
102 if (message.length() > 4096) {
103 message = message.substring(0, 256) + "... TRUNCATED!\n\n";
104 }
105 try {
106 writer.write(format(
107 "*** %s - %s",
108 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S")
109 .format(new Date()),
110 message));
111 } catch (IOException e) {
112 log.error("Unable to write gRPC call log", e);
113 }
114 }
115 }
116 }
117
118 @Override
119 public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
120 MethodDescriptor<ReqT, RespT> methodDescriptor,
121 CallOptions callOptions, Channel channel) {
122 return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
123 channel.newCall(methodDescriptor, callOptions.withoutWaitForReady())) {
124
125 private final long callId = callIdGenerator.getAndIncrement();
126
127 @Override
128 public void sendMessage(ReqT message) {
129 if (enabled.get()) {
130 write(format(
131 "%s >> OUTBOUND >> [callId=%s]\n%s\n",
132 methodDescriptor.getFullMethodName(),
133 callId,
134 message.toString()));
135 }
136 super.sendMessage(message);
137 }
138
139 @Override
140 public void start(Listener<RespT> responseListener, Metadata headers) {
141
142 if (enabled.get()) {
143 write(format(
144 "%s STARTED [callId=%s]\n%s\n\n",
145 methodDescriptor.getFullMethodName(),
146 callId,
147 headers.toString()));
148 }
149
150 Listener<RespT> listener = new ForwardingClientCallListener<RespT>() {
151 @Override
152 protected Listener<RespT> delegate() {
153 return responseListener;
154 }
155
156 @Override
157 public void onMessage(RespT message) {
158 if (enabled.get()) {
159 write(format(
160 "%s << INBOUND << [callId=%s]\n%s\n",
161 methodDescriptor.getFullMethodName(),
162 callId,
163 message.toString()));
164 }
165 super.onMessage(message);
166 }
167
168 @Override
169 public void onClose(Status status, Metadata trailers) {
170 if (enabled.get()) {
171 write(format(
172 "%s CLOSED [callId=%s]\n%s\n%s\n\n",
173 methodDescriptor.getFullMethodName(),
174 callId,
175 status.toString(),
176 parseTrailers(trailers)));
177 }
178 super.onClose(status, trailers);
179 }
180
181 private String parseTrailers(Metadata trailers) {
182 StringJoiner joiner = new StringJoiner("\n");
183 joiner.add(trailers.toString());
184 // If Google's RPC Status trailers are found, parse them.
185 final Iterable<com.google.rpc.Status> statuses = trailers.getAll(
186 GRPC_STATUS_KEY);
187 if (statuses == null) {
188 return joiner.toString();
189 }
190 statuses.forEach(s -> joiner.add(s.toString()));
191 return joiner.toString();
192 }
193
194 @Override
195 public void onHeaders(Metadata headers) {
196 super.onHeaders(headers);
197 }
198 };
199
200 super.start(listener, headers);
201 }
202 };
203 }
204}