Module: GrpcInterceptors::Common::Logging

Defined in:
lib/grpc_interceptors/common/logging.rb

Constant Summary collapse

SYMBOL_TO_SEVERITY =
{
  trace: ::Logger::Severity::DEBUG,
  debug: ::Logger::Severity::DEBUG,
  info: ::Logger::Severity::INFO,
  warn: ::Logger::Severity::WARN,
  error: ::Logger::Severity::ERROR,
  fatal: ::Logger::Severity::FATAL
}.freeze

Class Method Summary collapse

Class Method Details

.build_payload(method, method_type, kind) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/grpc_interceptors/common/logging.rb', line 86

def self.build_payload(method, method_type, kind)
  service = GrpcHelper.service_name(method)
  method = GrpcHelper.method_name(method)

  payload = {
    'pid' => Process.pid,
    'grpc.component' => kind, # the caller, server or client
    'grpc.service' => service,
    'grpc.method' => method,
    'grpc.method_type' => method_type
  }

  if defined?(OpenTelemetry) && OpenTelemetry::Trace.current_span.recording?
    tracing_context = OpenTelemetry::Trace.current_span.context
    payload['span_id'] = tracing_context.hex_span_id
    payload['trace_id'] = tracing_context.hex_trace_id
  end

  payload
end

.log(logger: nil, request: nil, method: nil, method_type: nil, kind: nil, extra_fields: {}) ⇒ Object

Log a gRPC interaction.

If the current log level is INFO, then it logs out basic facts. If the current log level is DEBUG, then it additionally adds to the log request and response. If the server responds with error, then the error is added to the log.

Parameters:

  • request (Object) (defaults to: nil)

    The request object

  • method (String) (defaults to: nil)

    The method passed to the intercepting function

  • method_type (String) (defaults to: nil)

    The used method_type, unary or one of the streams



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/grpc_interceptors/common/logging.rb', line 70

def self.log(
  logger: nil, request: nil, method: nil, method_type: nil, kind: nil,
  extra_fields: {}
)
  payload = build_payload(method, method_type, kind)
  payload.merge!(extra_fields)

  level = normalized_level(logger)
  if level == ::Logger::Severity::INFO
    logger.info(payload)
  elsif level == ::Logger::Severity::DEBUG
    payload['request'] = Common::GrpcHelper.proto_to_h(request) unless request.nil?
    logger.debug(payload)
  end
end

.normalized_level(logger) ⇒ Object

Some logger implementations (e.g. SemanticLogger-backed CasinoLogger) expose #level as a Symbol rather than the ::Logger::Severity Integer constants this module compares against. Normalize here so any logger duck-typed with #level/#info/#debug works without a per-service wrapper.



21
22
23
24
25
26
27
28
29
# File 'lib/grpc_interceptors/common/logging.rb', line 21

def self.normalized_level(logger)
  raw = logger.respond_to?(:level) ? logger.level : ::Logger::Severity::INFO

  case raw
  when Integer then raw
  when Symbol then SYMBOL_TO_SEVERITY.fetch(raw, ::Logger::Severity::INFO)
  else ::Logger::Severity::INFO
  end
end

.yield_and_log(logger: nil, request: nil, method: nil, method_type: nil, kind: nil) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/grpc_interceptors/common/logging.rb', line 31

def self.yield_and_log(
  logger: nil, request: nil, method: nil, method_type: nil, kind: nil
)
  grpc_code = ::GRPC::Core::StatusCodes::OK
  response = yield
rescue StandardError => e
  grpc_code = e.is_a?(::GRPC::BadStatus) ? e.code : ::GRPC::Core::StatusCodes::UNKNOWN
  extra_fields = {
    'error' => e.class.to_s,
    'error_message' => e.message,
    'backtrace' => e.backtrace
  }

  raise
ensure
  extra_fields ||= {}
  extra_fields['grpc.code'] = grpc_code

  if normalized_level(logger) == ::Logger::Severity::DEBUG && !response.nil? && !response.is_a?(Enumerator)
    extra_fields['response'] = Common::GrpcHelper.proto_to_h(response)
  end

  log(
    logger: logger, request: request, method: method,
    method_type: method_type, kind: kind, extra_fields: extra_fields
  )
end