- Added detailed comments to clarify the purpose of various functions and types in the Discord bot and HA gateway. - Introduced new methods in the CommandApp for handling light and switch operations, including HandleLightOn, HandleLightOff, HandleLightToggle, and their respective autocomplete functions. - Updated the HAClient interface to include methods for fetching states and calling services, enhancing the interaction with Home Assistant. - Improved the structure of entity and light domain models to include additional attributes and clearer documentation. - Implemented logging enhancements in both the Discord bot and HA gateway to ensure better traceability and context in logs. - Refactored the configuration loading process to streamline environment variable handling and defaults. - Stubbed out switch control methods in the gRPC adapter, indicating future implementation plans. - Enhanced telemetry setup to ensure proper initialization and shutdown procedures for observability.
89 lines
2.7 KiB
Go
89 lines
2.7 KiB
Go
package grpc
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/logger"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/peer"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
// LoggingUnaryInterceptor logs one completion record for each unary gRPC call.
|
|
func LoggingUnaryInterceptor(log *slog.Logger) grpc.UnaryServerInterceptor {
|
|
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
|
method, ok := grpc.Method(ctx)
|
|
if !ok {
|
|
method = info.FullMethod
|
|
}
|
|
reqLog := requestLogger(ctx, log, method)
|
|
ctx = logger.WithLogger(ctx, reqLog)
|
|
|
|
start := time.Now()
|
|
resp, err := handler(ctx, req)
|
|
logCompletion(reqLog, "grpc call completed", status.Code(err), time.Since(start), err)
|
|
return resp, err
|
|
}
|
|
}
|
|
|
|
// LoggingStreamInterceptor logs stream start and completion with request-scoped fields.
|
|
func LoggingStreamInterceptor(log *slog.Logger) grpc.StreamServerInterceptor {
|
|
return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
|
reqLog := requestLogger(ss.Context(), log, info.FullMethod)
|
|
reqLog.Info("grpc stream started")
|
|
|
|
wrapped := &loggingServerStream{
|
|
ServerStream: ss,
|
|
ctx: logger.WithLogger(ss.Context(), reqLog),
|
|
}
|
|
start := time.Now()
|
|
err := handler(srv, wrapped)
|
|
logCompletion(reqLog, "grpc stream completed", status.Code(err), time.Since(start), err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
type loggingServerStream struct {
|
|
grpc.ServerStream
|
|
ctx context.Context
|
|
}
|
|
|
|
// Context returns the request-scoped context with the derived logger attached.
|
|
func (s *loggingServerStream) Context() context.Context {
|
|
return s.ctx
|
|
}
|
|
|
|
// requestLogger derives a child logger so every downstream component sees the
|
|
// same gRPC method and peer metadata through context propagation.
|
|
func requestLogger(ctx context.Context, log *slog.Logger, method string) *slog.Logger {
|
|
peerAddr := ""
|
|
if p, ok := peer.FromContext(ctx); ok && p.Addr != nil {
|
|
peerAddr = p.Addr.String()
|
|
}
|
|
return log.With("grpc.method", method, "grpc.peer", peerAddr)
|
|
}
|
|
|
|
// logCompletion keeps severity consistent with gRPC status semantics so
|
|
// expected client-facing errors do not look like infrastructure failures.
|
|
func logCompletion(log *slog.Logger, msg string, code codes.Code, duration time.Duration, err error) {
|
|
attrs := []any{
|
|
"duration_ms", duration.Milliseconds(),
|
|
"grpc.code", code.String(),
|
|
}
|
|
if err != nil {
|
|
attrs = append(attrs, "error", err.Error())
|
|
}
|
|
|
|
switch code {
|
|
case codes.OK:
|
|
log.Info(msg, attrs...)
|
|
case codes.NotFound, codes.InvalidArgument, codes.Unimplemented:
|
|
log.Warn(msg, attrs...)
|
|
default:
|
|
log.Error(msg, attrs...)
|
|
}
|
|
}
|