Skip to content
Permalink
Browse files
feat: support http_request field (#120)
  • Loading branch information
daniel-sanche committed Dec 16, 2020
1 parent 2a36af6 commit ba94afb7d0a5371f2d2de4232de56df34e8a1f99
@@ -23,9 +23,14 @@
flask = None

from google.cloud.logging_v2.handlers.middleware.request import _get_django_request
from google.logging.type.http_request_pb2 import HttpRequest

_DJANGO_TRACE_HEADER = "HTTP_X_CLOUD_TRACE_CONTEXT"
_DJANGO_USERAGENT_HEADER = "HTTP_USER_AGENT"
_DJANGO_REMOTE_ADDR_HEADER = "REMOTE_ADDR"
_DJANGO_REFERER_HEADER = "HTTP_REFERER"
_FLASK_TRACE_HEADER = "X_CLOUD_TRACE_CONTEXT"
_PROTOCOL_HEADER = "SERVER_PROTOCOL"


def format_stackdriver_json(record, message):
@@ -46,59 +51,86 @@ def format_stackdriver_json(record, message):
return json.dumps(payload)


def get_trace_id_from_flask():
"""Get trace_id from flask request headers.
def get_request_data_from_flask():
"""Get http_request and trace data from flask request headers.
Returns:
str: TraceID in HTTP request headers.
Tuple[Optional[google.logging.type.http_request_pb2.HttpRequest], Optional[str]]:
Data related to the current http request and the trace_id for the
request. Both fields will be None if a flask request isn't found.
"""
if flask is None or not flask.request:
return None
return None, None

# build http_request
http_request = HttpRequest(
request_method=flask.request.method,
request_url=flask.request.url,
request_size=flask.request.content_length,
user_agent=flask.request.user_agent.string,
remote_ip=flask.request.remote_addr,
referer=flask.request.referrer,
protocol=flask.request.environ.get(_PROTOCOL_HEADER),
)

# find trace id
trace_id = None
header = flask.request.headers.get(_FLASK_TRACE_HEADER)
if header:
trace_id = header.split("/", 1)[0]

if header is None:
return None

trace_id = header.split("/", 1)[0]

return trace_id
return http_request, trace_id


def get_trace_id_from_django():
"""Get trace_id from django request headers.
def get_request_data_from_django():
"""Get http_request and trace data from django request headers.
Returns:
str: TraceID in HTTP request headers.
Tuple[Optional[google.logging.type.http_request_pb2.HttpRequest], Optional[str]]:
Data related to the current http request and the trace_id for the
request. Both fields will be None if a django request isn't found.
"""
request = _get_django_request()

if request is None:
return None
return None, None
# build http_request
http_request = HttpRequest(
request_method=request.method,
request_url=request.build_absolute_uri(),
request_size=len(request.body),
user_agent=request.META.get(_DJANGO_USERAGENT_HEADER),
remote_ip=request.META.get(_DJANGO_REMOTE_ADDR_HEADER),
referer=request.META.get(_DJANGO_REFERER_HEADER),
protocol=request.META.get(_PROTOCOL_HEADER),
)

# find trace id
trace_id = None
header = request.META.get(_DJANGO_TRACE_HEADER)
if header is None:
return None

trace_id = header.split("/", 1)[0]
if header:
trace_id = header.split("/", 1)[0]

return trace_id
return http_request, trace_id


def get_trace_id():
"""Helper to get trace_id from web application request header.
def get_request_data():
"""Helper to get http_request and trace data from supported web
frameworks (currently supported: Flask and Django).
Returns:
str: TraceID in HTTP request headers.
Tuple[Optional[google.logging.type.http_request_pb2.HttpRequest], Optional[str]]:
Data related to the current http request and the trace_id for the
request. Both fields will be None if a supported web request isn't found.
"""
checkers = (
get_trace_id_from_django,
get_trace_id_from_flask,
get_request_data_from_django,
get_request_data_from_flask,
)

for checker in checkers:
trace_id = checker()
if trace_id is not None:
return trace_id
http_request, trace_id = checker()
if http_request is not None:
return http_request, trace_id

return None
return None, None
@@ -21,7 +21,7 @@
import logging
import os

from google.cloud.logging_v2.handlers._helpers import get_trace_id
from google.cloud.logging_v2.handlers._helpers import get_request_data
from google.cloud.logging_v2.handlers.transports import BackgroundThreadTransport
from google.cloud.logging_v2.resource import Resource

@@ -96,7 +96,7 @@ def get_gae_labels(self):
"""
gae_labels = {}

trace_id = get_trace_id()
_, trace_id = get_request_data()
if trace_id is not None:
gae_labels[_TRACE_ID_LABEL] = trace_id

@@ -114,11 +114,14 @@ def emit(self, record):
"""
message = super(AppEngineHandler, self).format(record)
gae_labels = self.get_gae_labels()
trace_id = (
"projects/%s/traces/%s" % (self.project_id, gae_labels[_TRACE_ID_LABEL])
if _TRACE_ID_LABEL in gae_labels
else None
)
http_request, trace_id = get_request_data()
if trace_id is not None:
trace_id = f"projects/{self.project_id}/traces/{trace_id}"
self.transport.send(
record, message, resource=self.resource, labels=gae_labels, trace=trace_id
record,
message,
resource=self.resource,
labels=gae_labels,
trace=trace_id,
http_request=http_request,
)
@@ -222,31 +222,21 @@ def _main_thread_terminated(self):
file=sys.stderr,
)

def enqueue(
self, record, message, *, resource=None, labels=None, trace=None, span_id=None
):
def enqueue(self, record, message, **kwargs):
"""Queues a log entry to be written by the background thread.
Args:
record (logging.LogRecord): Python log record that the handler was called with.
message (str): The message from the ``LogRecord`` after being
formatted by the associated log formatters.
resource (Optional[google.cloud.logging_v2.resource.Resource]):
Monitored resource of the entry
labels (Optional[dict]): Mapping of labels for the entry.
trace (Optional[str]): TraceID to apply to the logging entry.
span_id (Optional[str]): Span_id within the trace for the log entry.
Specify the trace parameter if span_id is set.
kwargs: Additional optional arguments for the logger
"""
queue_entry = {
"info": {"message": message, "python_logger": record.name},
"severity": _helpers._normalize_severity(record.levelno),
"resource": resource,
"labels": labels,
"trace": trace,
"span_id": span_id,
"timestamp": datetime.datetime.utcfromtimestamp(record.created),
}
queue_entry.update(kwargs)
self._queue.put_nowait(queue_entry)

def flush(self):
@@ -291,30 +281,16 @@ def __init__(
)
self.worker.start()

def send(
self, record, message, resource=None, labels=None, trace=None, span_id=None
):
def send(self, record, message, **kwargs):
"""Overrides Transport.send().
Args:
record (logging.LogRecord): Python log record that the handler was called with.
message (str): The message from the ``LogRecord`` after being
formatted by the associated log formatters.
resource (Optional[google.cloud.logging_v2.resource.Resource]):
Monitored resource of the entry.
labels (Optional[dict]): Mapping of labels for the entry.
trace (Optional[str]): TraceID to apply to the logging entry.
span_id (Optional[str]): span_id within the trace for the log entry.
Specify the trace parameter if span_id is set.
kwargs: Additional optional arguments for the logger
"""
self.worker.enqueue(
record,
message,
resource=resource,
labels=labels,
trace=trace,
span_id=span_id,
)
self.worker.enqueue(record, message, **kwargs)

def flush(self):
"""Submit any pending log records."""
@@ -22,18 +22,14 @@ class Transport(object):
client and name object, and must override :meth:`send`.
"""

def send(
self, record, message, *, resource=None, labels=None, trace=None, span_id=None
):
def send(self, record, message, **kwargs):
"""Transport send to be implemented by subclasses.
Args:
record (logging.LogRecord): Python log record that the handler was called with.
message (str): The message from the ``LogRecord`` after being
formatted by the associated log formatters.
resource (Optional[google.cloud.logging_v2.resource.Resource]):
Monitored resource of the entry.
labels (Optional[dict]): Mapping of labels for the entry.
kwargs: Additional optional arguments for the logger
"""
raise NotImplementedError

@@ -30,26 +30,17 @@ class SyncTransport(Transport):
def __init__(self, client, name):
self.logger = client.logger(name)

def send(
self, record, message, *, resource=None, labels=None, trace=None, span_id=None
):
def send(self, record, message, **kwargs):
"""Overrides transport.send().
Args:
record (logging.LogRecord):
Python log record that the handler was called with.
message (str): The message from the ``LogRecord`` after being
formatted by the associated log formatters.
resource (Optional[~logging_v2.resource.Resource]):
Monitored resource of the entry.
labels (Optional[dict]): Mapping of labels for the entry.
kwargs: Additional optional arguments for the logger
"""
info = {"message": message, "python_logger": record.name}
self.logger.log_struct(
info,
severity=_helpers._normalize_severity(record.levelno),
resource=resource,
labels=labels,
trace=trace,
span_id=span_id,
info, severity=_helpers._normalize_severity(record.levelno), **kwargs,
)

0 comments on commit ba94afb

Please sign in to comment.