Skip to content
This repository was archived by the owner on Aug 13, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a78f577
feat: use standard output logs on serverless environments (#228)
daniel-sanche Apr 9, 2021
a5c2f8e
feat: record source locations (#254)
daniel-sanche Apr 16, 2021
6b10b74
feat: Improve source location overrides (#258)
daniel-sanche Apr 20, 2021
fe4de39
feat: allow custom labels with standard library logging (#264)
daniel-sanche Apr 22, 2021
fcd26eb
feat: support span inference (#267)
daniel-sanche May 3, 2021
1e77860
refactor: clean up CloudLoggingFilter (#281)
daniel-sanche May 5, 2021
998ff09
chore: added owlbot to gitignore
daniel-sanche May 6, 2021
874fdfa
fix(deps): fix minimum required version of google-api-core (#244)
yoshi-automation Apr 2, 2021
f6a9f51
chore: remove logging-v2-py.tar.gz from file tracking (#248)
yoshi-automation Apr 2, 2021
ad08515
build(python): update docfx job to use new plugin (#250)
yoshi-automation Apr 5, 2021
a37fc80
chore: Add license headers for python config files (#253)
yoshi-automation Apr 7, 2021
bb49f93
chore(deps): update dependency google-cloud-storage to v1.37.0 (#243)
renovate-bot Apr 7, 2021
c1452ac
chore: prevent normalization of semver versioning (#259)
dandhlee Apr 16, 2021
7541620
chore(deps): update dependency google-cloud-storage to v1.37.1 (#255)
renovate-bot Apr 20, 2021
359279c
chore: Re-generated to pick up changes from self (#260)
yoshi-automation Apr 20, 2021
081f265
chore: migrate to owl bot (#270)
parthea Apr 26, 2021
324290c
chore(deps): update dependency google-cloud-bigquery to v2.14.0 (#271)
renovate-bot Apr 26, 2021
7fe7855
chore(revert): revert preventing normalization (#269)
dandhlee Apr 27, 2021
c1ab3fb
chore(deps): update dependency google-cloud-bigquery to v2.15.0 (#277)
renovate-bot May 3, 2021
78a5ea9
chore: add SECURITY.md (#275)
google-cloud-policy-bot[bot] May 3, 2021
2f5ed98
chore(deps): update dependency pytest to v6.2.3 (#273)
renovate-bot May 3, 2021
ae3aa1c
chore(deps): update dependency google-cloud-storage to v1.38.0 (#272)
renovate-bot May 3, 2021
1e1e053
chore(deps): update dependency pytest to v6.2.4 (#280)
renovate-bot May 4, 2021
b7196d0
chore(deps): update dependency google-cloud-bigquery to v2.16.0 (#282)
renovate-bot May 6, 2021
eee1126
chore: new owl bot post processor docker image (#287)
gcf-owl-bot[bot] May 10, 2021
502aeb1
chore: add library type to .repo-metadata.json (#285)
parthea May 10, 2021
8872d6f
fix: changed region format on serverless (#291)
daniel-sanche May 10, 2021
3324656
chore(deps): update dependency google-cloud-pubsub to v2.4.2 (#288)
renovate-bot May 10, 2021
bdf8273
fix: remove noisy logs (#290)
daniel-sanche May 11, 2021
1f9517d
fix: improve API compatibility for next release (#292)
daniel-sanche May 12, 2021
40b9c61
Merge branch 'master' into v2_update_2_
daniel-sanche May 12, 2021
04cba9f
chore: merged environment tests to main
daniel-sanche May 12, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions google/cloud/logging/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@

from google.cloud.logging_v2.handlers.app_engine import AppEngineHandler
from google.cloud.logging_v2.handlers.container_engine import ContainerEngineHandler
from google.cloud.logging_v2.handlers.structured_log import StructuredLogHandler
from google.cloud.logging_v2.handlers.handlers import CloudLoggingFilter
from google.cloud.logging_v2.handlers.handlers import CloudLoggingHandler
from google.cloud.logging_v2.handlers.handlers import setup_logging

__all__ = [
"AppEngineHandler",
"CloudLoggingFilter",
"CloudLoggingHandler",
"ContainerEngineHandler",
"StructuredLogHandler",
"setup_logging",
]
31 changes: 19 additions & 12 deletions google/cloud/logging_v2/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import logging
import os
import sys

try:
from google.cloud.logging_v2 import _gapic
Expand All @@ -36,6 +37,7 @@
from google.cloud.logging_v2.handlers import CloudLoggingHandler
from google.cloud.logging_v2.handlers import AppEngineHandler
from google.cloud.logging_v2.handlers import ContainerEngineHandler
from google.cloud.logging_v2.handlers import StructuredLogHandler
from google.cloud.logging_v2.handlers import setup_logging
from google.cloud.logging_v2.handlers.handlers import EXCLUDED_LOGGER_DEFAULTS
from google.cloud.logging_v2.resource import Resource
Expand All @@ -53,6 +55,7 @@
_GAE_RESOURCE_TYPE = "gae_app"
_GKE_RESOURCE_TYPE = "k8s_container"
_GCF_RESOURCE_TYPE = "cloud_function"
_RUN_RESOURCE_TYPE = "cloud_run_revision"


class Client(ClientWithProject):
Expand Down Expand Up @@ -347,18 +350,22 @@ def get_default_handler(self, **kw):
"""
monitored_resource = kw.pop("resource", detect_resource(self.project))

if (
isinstance(monitored_resource, Resource)
and monitored_resource.type == _GAE_RESOURCE_TYPE
):
return AppEngineHandler(self, **kw)
elif (
isinstance(monitored_resource, Resource)
and monitored_resource.type == _GKE_RESOURCE_TYPE
):
return ContainerEngineHandler(**kw)
else:
return CloudLoggingHandler(self, resource=monitored_resource, **kw)
if isinstance(monitored_resource, Resource):
if monitored_resource.type == _GAE_RESOURCE_TYPE:
return AppEngineHandler(self, **kw)
elif monitored_resource.type == _GKE_RESOURCE_TYPE:
return ContainerEngineHandler(**kw)
elif (
monitored_resource.type == _GCF_RESOURCE_TYPE
and sys.version_info[0] == 3
and sys.version_info[1] >= 8
):
# Cloud Functions with runtimes > 3.8 supports structured logs on standard out
# 3.7 should use the standard CloudLoggingHandler, which sends logs over the network.
return StructuredLogHandler(**kw, project_id=self.project)
elif monitored_resource.type == _RUN_RESOURCE_TYPE:
return StructuredLogHandler(**kw, project_id=self.project)
return CloudLoggingHandler(self, resource=monitored_resource, **kw)

def setup_logging(
self, *, log_level=logging.INFO, excluded_loggers=EXCLUDED_LOGGER_DEFAULTS, **kw
Expand Down
4 changes: 4 additions & 0 deletions google/cloud/logging_v2/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@

from google.cloud.logging_v2.handlers.app_engine import AppEngineHandler
from google.cloud.logging_v2.handlers.container_engine import ContainerEngineHandler
from google.cloud.logging_v2.handlers.structured_log import StructuredLogHandler
from google.cloud.logging_v2.handlers.handlers import CloudLoggingHandler
from google.cloud.logging_v2.handlers.handlers import CloudLoggingFilter
from google.cloud.logging_v2.handlers.handlers import setup_logging

__all__ = [
"AppEngineHandler",
"CloudLoggingFilter",
"CloudLoggingHandler",
"ContainerEngineHandler",
"StructuredLogHandler",
"setup_logging",
]
73 changes: 49 additions & 24 deletions google/cloud/logging_v2/handlers/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import math
import json
import re

try:
import flask
Expand Down Expand Up @@ -55,12 +56,13 @@ def get_request_data_from_flask():
"""Get http_request and trace data from flask request headers.

Returns:
Tuple[Optional[dict], 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.
Tuple[Optional[dict], Optional[str], Optional[str]]:
Data related to the current http request, trace_id, and span_id for
the request. All fields will be None if a django request isn't
found.
"""
if flask is None or not flask.request:
return None, None
return None, None, None

# build http_request
http_request = {
Expand All @@ -73,34 +75,34 @@ def get_request_data_from_flask():
"protocol": flask.request.environ.get(_PROTOCOL_HEADER),
}

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

return http_request, trace_id
return http_request, trace_id, span_id


def get_request_data_from_django():
"""Get http_request and trace data from django request headers.

Returns:
Tuple[Optional[dict], 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.
Tuple[Optional[dict], Optional[str], Optional[str]]:
Data related to the current http request, trace_id, and span_id for
the request. All fields will be None if a django request isn't
found.
"""
request = _get_django_request()

if request is None:
return None, None
return None, None, None

# convert content_length to int if it exists
content_length = None
try:
content_length = int(request.META.get(_DJANGO_CONTENT_LENGTH))
except (ValueError, TypeError):
content_length = None

# build http_request
http_request = {
"requestMethod": request.method,
Expand All @@ -112,32 +114,55 @@ def get_request_data_from_django():
"protocol": request.META.get(_PROTOCOL_HEADER),
}

# find trace id
trace_id = None
# find trace id and span id
header = request.META.get(_DJANGO_TRACE_HEADER)
if header:
trace_id = header.split("/", 1)[0]
trace_id, span_id = _parse_trace_span(header)

return http_request, trace_id, span_id

return http_request, trace_id

def _parse_trace_span(header):
"""Given an X_CLOUD_TRACE header, extract the trace and span ids.

Args:
header (str): the string extracted from the X_CLOUD_TRACE header
Returns:
Tuple[Optional[dict], Optional[str]]:
The trace_id and span_id extracted from the header
Each field will be None if not found.
"""
trace_id = None
span_id = None
if header:
try:
split_header = header.split("/", 1)
trace_id = split_header[0]
header_suffix = split_header[1]
# the span is the set of alphanumeric characters after the /
span_id = re.findall(r"^\w+", header_suffix)[0]
except IndexError:
pass
return trace_id, span_id


def get_request_data():
"""Helper to get http_request and trace data from supported web
frameworks (currently supported: Flask and Django).

Returns:
Tuple[Optional[dict], 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.
Tuple[Optional[dict], Optional[str], Optional[str]]:
Data related to the current http request, trace_id, and span_id for
the request. All fields will be None if a django request isn't
found.
"""
checkers = (
get_request_data_from_django,
get_request_data_from_flask,
)

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

return None, None
return None, None, None
4 changes: 2 additions & 2 deletions google/cloud/logging_v2/handlers/app_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def get_gae_labels(self):
"""
gae_labels = {}

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

Expand All @@ -107,7 +107,7 @@ def emit(self, record):
record (logging.LogRecord): The record to be logged.
"""
message = super(AppEngineHandler, self).format(record)
inferred_http, inferred_trace = get_request_data()
inferred_http, inferred_trace, _ = get_request_data()
if inferred_trace is not None:
inferred_trace = f"projects/{self.project_id}/traces/{inferred_trace}"
# allow user overrides
Expand Down
Loading