gRPC service utilities providing health checking, server reflection, channelz observability, and binary logging capabilities
—
Request and response logging system for debugging, auditing, and compliance. Binary logging captures detailed information about gRPC calls including headers, messages, and metadata in a structured format.
Factory class for creating binary logging instances with different configurations and sinks.
/**
* Utility class for creating binary logging instances.
* Provides factory methods for different logging configurations.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4017")
public final class BinaryLogs {
/**
* Creates a binary log that writes to a temp file
* @return BinaryLog instance configured with temporary file sink
* @throws IOException if temp file cannot be created
*/
public static BinaryLog createBinaryLog() throws IOException;
/**
* Creates a binary log with custom sink and config string
* @param sink Custom sink for writing log messages
* @param configStr Config string as defined by A16-binary-logging specification
* @return BinaryLog instance with custom configuration
* @throws IOException if sink initialization fails
*/
public static BinaryLog createBinaryLog(
BinaryLogSink sink,
String configStr
) throws IOException;
/**
* Creates a binary log with sink only (deprecated)
* @param sink Custom sink for writing log messages
* @return BinaryLog instance
* @throws IOException if sink initialization fails
* @deprecated Use createBinaryLog(BinaryLogSink, String) instead
*/
@Deprecated
public static BinaryLog createBinaryLog(BinaryLogSink sink) throws IOException;
}Usage Examples:
import io.grpc.BinaryLog;
import io.grpc.ServerBuilder;
import io.grpc.protobuf.services.BinaryLogs;
import io.grpc.protobuf.services.TempFileSink;
// Simple binary logging to temp file
BinaryLog binaryLog = BinaryLogs.createBinaryLog();
Server server = ServerBuilder.forPort(8080)
.setBinaryLog(binaryLog)
.addService(new MyService())
.build();
// Custom binary logging with configuration
BinaryLogSink customSink = new TempFileSink();
String config = "*"; // Log all methods
BinaryLog configuredLog = BinaryLogs.createBinaryLog(customSink, config);
Server configuredServer = ServerBuilder.forPort(8081)
.setBinaryLog(configuredLog)
.addService(new MyService())
.build();Interface for implementing custom binary log sinks that handle the actual storage or transmission of log data.
/**
* Interface for accepting binary log messages.
* Implement this interface to create custom log storage backends.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4017")
public interface BinaryLogSink extends Closeable {
/**
* Writes the message to the destination
* @param message Protocol buffer message containing log data
*/
void write(MessageLite message);
}Custom Sink Implementation Example:
import io.grpc.protobuf.services.BinaryLogSink;
import com.google.protobuf.MessageLite;
import java.io.FileOutputStream;
import java.io.IOException;
public class CustomFileSink implements BinaryLogSink {
private final FileOutputStream outputStream;
public CustomFileSink(String filename) throws IOException {
this.outputStream = new FileOutputStream(filename, true); // Append mode
}
@Override
public void write(MessageLite message) {
try {
// Write message size first
byte[] messageBytes = message.toByteArray();
byte[] sizeBytes = ByteBuffer.allocate(4)
.putInt(messageBytes.length)
.array();
outputStream.write(sizeBytes);
outputStream.write(messageBytes);
outputStream.flush();
} catch (IOException e) {
System.err.println("Failed to write binary log: " + e.getMessage());
}
}
@Override
public void close() throws IOException {
if (outputStream != null) {
outputStream.close();
}
}
}Built-in implementation that writes logs to temporary files.
/**
* Binary log sink that writes to temporary files.
* Useful for development and debugging scenarios.
*/
public class TempFileSink implements BinaryLogSink {
/** Creates a temp file sink that writes to a system temporary file */
public TempFileSink() throws IOException;
}Usage Example:
import io.grpc.protobuf.services.TempFileSink;
import io.grpc.protobuf.services.BinaryLogs;
// Create binary log with temp file sink
BinaryLogSink tempSink = new TempFileSink();
BinaryLog binaryLog = BinaryLogs.createBinaryLog(tempSink, "*");
// Use with server
Server server = ServerBuilder.forPort(8080)
.setBinaryLog(binaryLog)
.addService(new MyService())
.build();
// Don't forget to close the sink when done
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
tempSink.close();
} catch (IOException e) {
System.err.println("Error closing binary log sink: " + e.getMessage());
}
}));Binary logging supports flexible configuration using the A16-binary-logging specification:
// Log all methods on all services
String configAll = "*";
BinaryLog logAll = BinaryLogs.createBinaryLog(sink, configAll);
// Log specific service
String configService = "com.example.UserService/*";
BinaryLog logService = BinaryLogs.createBinaryLog(sink, configService);
// Log specific method
String configMethod = "com.example.UserService/GetUser";
BinaryLog logMethod = BinaryLogs.createBinaryLog(sink, configMethod);
// Log with size limits (header and message bytes)
String configLimited = "*{h:256;m:1024}";
BinaryLog logLimited = BinaryLogs.createBinaryLog(sink, configLimited);public class BinaryLogConfiguration {
public static BinaryLog createProductionLog() throws IOException {
// Production logging: limited data, specific services only
BinaryLogSink sink = new ProductionLogSink();
// Log only critical services with size limits
String config = "com.example.PaymentService/*{h:512;m:2048}," +
"com.example.AuthService/*{h:256;m:1024}";
return BinaryLogs.createBinaryLog(sink, config);
}
public static BinaryLog createDevelopmentLog() throws IOException {
// Development logging: full data for debugging
BinaryLogSink sink = new TempFileSink();
// Log everything with large limits
String config = "*{h:8192;m:65536}";
return BinaryLogs.createBinaryLog(sink, config);
}
public static BinaryLog createAuditLog() throws IOException {
// Audit logging: specific methods only
BinaryLogSink sink = new AuditLogSink();
// Log only sensitive operations
String config = "com.example.UserService/CreateUser," +
"com.example.UserService/DeleteUser," +
"com.example.PaymentService/ProcessPayment";
return BinaryLogs.createBinaryLog(sink, config);
}
}import io.grpc.protobuf.services.BinaryLogSink;
import com.google.protobuf.MessageLite;
import io.grpc.binarylog.v1.GrpcLogEntry;
public class MonitoringLogSink implements BinaryLogSink {
private final Logger logger = LoggerFactory.getLogger(MonitoringLogSink.class);
@Override
public void write(MessageLite message) {
if (message instanceof GrpcLogEntry) {
GrpcLogEntry logEntry = (GrpcLogEntry) message;
// Extract key information for structured logging
Map<String, Object> logData = new HashMap<>();
logData.put("timestamp", logEntry.getTimestamp());
logData.put("type", logEntry.getType().name());
logData.put("logger", logEntry.getLogger().name());
if (logEntry.hasCallId()) {
logData.put("callId", logEntry.getCallId());
}
if (logEntry.hasClientHeader()) {
logData.put("method", logEntry.getClientHeader().getMethodName());
logData.put("authority", logEntry.getClientHeader().getAuthority());
}
// Send to monitoring system
logger.info("gRPC call logged: {}", logData);
}
}
@Override
public void close() throws IOException {
// Cleanup monitoring connections if needed
}
}public class PerformanceAwareBinaryLog {
private final BinaryLog binaryLog;
private final AtomicLong logCount = new AtomicLong(0);
public PerformanceAwareBinaryLog() throws IOException {
// Create custom sink that tracks performance
BinaryLogSink performanceSink = new BinaryLogSink() {
private final BinaryLogSink delegate = new TempFileSink();
@Override
public void write(MessageLite message) {
long count = logCount.incrementAndGet();
// Log performance warning if logging rate is high
if (count % 10000 == 0) {
System.out.println("Binary log count: " + count);
}
delegate.write(message);
}
@Override
public void close() throws IOException {
delegate.close();
}
};
// Use moderate configuration to balance debugging and performance
String config = "*{h:1024;m:4096}";
this.binaryLog = BinaryLogs.createBinaryLog(performanceSink, config);
}
public BinaryLog getBinaryLog() {
return binaryLog;
}
public long getLogCount() {
return logCount.get();
}
}Binary logging can capture sensitive data, so proper handling is essential:
public class SecureBinaryLogSink implements BinaryLogSink {
private final BinaryLogSink delegate;
private final Set<String> sensitiveHeaders;
public SecureBinaryLogSink(BinaryLogSink delegate) {
this.delegate = delegate;
this.sensitiveHeaders = Set.of(
"authorization",
"x-api-key",
"cookie"
);
}
@Override
public void write(MessageLite message) {
// In a real implementation, you would need to parse and sanitize
// the message to remove sensitive data before delegating
// For demonstration, we'll just delegate
// In practice, implement message filtering here
delegate.write(message);
}
@Override
public void close() throws IOException {
delegate.close();
}
}
// Usage
BinaryLogSink secureHttp = new SecureBinaryLogSink(new TempFileSink());
BinaryLog secureLog = BinaryLogs.createBinaryLog(secureHttp, "*{h:512;m:0}"); // Headers only, no message bodiesInstall with Tessl CLI
npx tessl i tessl/maven-io-grpc--grpc-services