Nik Afiq 520f5d1ffb
Some checks failed
CI / build-ai-gateway (push) Has been cancelled
CI / build-ha-gateway (push) Has been cancelled
CI / build-discord-bot (push) Has been cancelled
CI / test (push) Has been cancelled
feat: add ai-gateway microservice with gRPC API for AI logic
- Implemented new gRPC service `AIService` in `proto/ai/v1/ai.proto` for handling natural language queries.
- Generated Go code for the gRPC service and messages in `gen/ai/v1/`.
- Created `services/ai-gateway/` directory structure with necessary files for the service.
- Added configuration loading and structured logging.
- Implemented domain logic for intent parsing and interaction with Home Assistant.
- Established outbound adapters for Ollama and Home Assistant with mTLS support.
- Updated `go.work` to include the new service and maintain existing dependencies.
- Modified `discord-bot` to use the new `ai-gateway` for AI interactions.
- Added deployment manifest for Kubernetes and CI/CD configuration for building and deploying the service.
2026-04-21 21:52:28 +09:00

62 lines
1.6 KiB
Go

package grpc
import (
"context"
"log/slog"
"time"
"gitea.nik4nao.com/nik/home-services/ai-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, req)
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
}
}
func requestLogger(ctx context.Context, log *slog.Logger, method string, req any) *slog.Logger {
peerAddr := ""
if p, ok := peer.FromContext(ctx); ok && p.Addr != nil {
peerAddr = p.Addr.String()
}
source := ""
if r, ok := req.(interface{ GetSource() string }); ok {
source = r.GetSource()
}
return log.With("grpc.method", method, "grpc.peer", peerAddr, "source", source)
}
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.InvalidArgument, codes.NotFound, codes.Unimplemented:
log.Warn(msg, attrs...)
default:
log.Error(msg, attrs...)
}
}