CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-grpc--grpc-services

gRPC service utilities providing health checking, server reflection, channelz observability, and binary logging capabilities

Pending
Overview
Eval results
Files

binary-logging.mddocs/

Binary Logging

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.

Capabilities

BinaryLogs

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();

BinaryLogSink

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();
        }
    }
}

TempFileSink

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());
    }
}));

Configuration Patterns

Binary logging supports flexible configuration using the A16-binary-logging specification:

Basic Configuration Examples

// 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);

Advanced Configuration

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);
    }
}

Integration with Monitoring Systems

Structured Logging Integration

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
    }
}

Performance Monitoring

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();
    }
}

Security and Compliance

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 bodies

Install with Tessl CLI

npx tessl i tessl/maven-io-grpc--grpc-services

docs

admin-services.md

binary-logging.md

channelz.md

health-checking.md

index.md

load-balancing.md

metrics.md

server-reflection.md

tile.json