Skip to content

Commit 440add2

Browse files
committed
Merge branch 'v2_update_2_'
2 parents 3649a62 + 04cba9f commit 440add2

File tree

14 files changed

+794
-116
lines changed

14 files changed

+794
-116
lines changed

google/cloud/logging/handlers/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616

1717
from google.cloud.logging_v2.handlers.app_engine import AppEngineHandler
1818
from google.cloud.logging_v2.handlers.container_engine import ContainerEngineHandler
19+
from google.cloud.logging_v2.handlers.structured_log import StructuredLogHandler
20+
from google.cloud.logging_v2.handlers.handlers import CloudLoggingFilter
1921
from google.cloud.logging_v2.handlers.handlers import CloudLoggingHandler
2022
from google.cloud.logging_v2.handlers.handlers import setup_logging
2123

2224
__all__ = [
2325
"AppEngineHandler",
26+
"CloudLoggingFilter",
2427
"CloudLoggingHandler",
2528
"ContainerEngineHandler",
29+
"StructuredLogHandler",
2630
"setup_logging",
2731
]

google/cloud/logging_v2/client.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import logging
1818
import os
19+
import sys
1920

2021
try:
2122
from google.cloud.logging_v2 import _gapic
@@ -36,6 +37,7 @@
3637
from google.cloud.logging_v2.handlers import CloudLoggingHandler
3738
from google.cloud.logging_v2.handlers import AppEngineHandler
3839
from google.cloud.logging_v2.handlers import ContainerEngineHandler
40+
from google.cloud.logging_v2.handlers import StructuredLogHandler
3941
from google.cloud.logging_v2.handlers import setup_logging
4042
from google.cloud.logging_v2.handlers.handlers import EXCLUDED_LOGGER_DEFAULTS
4143
from google.cloud.logging_v2.resource import Resource
@@ -53,6 +55,7 @@
5355
_GAE_RESOURCE_TYPE = "gae_app"
5456
_GKE_RESOURCE_TYPE = "k8s_container"
5557
_GCF_RESOURCE_TYPE = "cloud_function"
58+
_RUN_RESOURCE_TYPE = "cloud_run_revision"
5659

5760

5861
class Client(ClientWithProject):
@@ -347,18 +350,22 @@ def get_default_handler(self, **kw):
347350
"""
348351
monitored_resource = kw.pop("resource", detect_resource(self.project))
349352

350-
if (
351-
isinstance(monitored_resource, Resource)
352-
and monitored_resource.type == _GAE_RESOURCE_TYPE
353-
):
354-
return AppEngineHandler(self, **kw)
355-
elif (
356-
isinstance(monitored_resource, Resource)
357-
and monitored_resource.type == _GKE_RESOURCE_TYPE
358-
):
359-
return ContainerEngineHandler(**kw)
360-
else:
361-
return CloudLoggingHandler(self, resource=monitored_resource, **kw)
353+
if isinstance(monitored_resource, Resource):
354+
if monitored_resource.type == _GAE_RESOURCE_TYPE:
355+
return AppEngineHandler(self, **kw)
356+
elif monitored_resource.type == _GKE_RESOURCE_TYPE:
357+
return ContainerEngineHandler(**kw)
358+
elif (
359+
monitored_resource.type == _GCF_RESOURCE_TYPE
360+
and sys.version_info[0] == 3
361+
and sys.version_info[1] >= 8
362+
):
363+
# Cloud Functions with runtimes > 3.8 supports structured logs on standard out
364+
# 3.7 should use the standard CloudLoggingHandler, which sends logs over the network.
365+
return StructuredLogHandler(**kw, project_id=self.project)
366+
elif monitored_resource.type == _RUN_RESOURCE_TYPE:
367+
return StructuredLogHandler(**kw, project_id=self.project)
368+
return CloudLoggingHandler(self, resource=monitored_resource, **kw)
362369

363370
def setup_logging(
364371
self, *, log_level=logging.INFO, excluded_loggers=EXCLUDED_LOGGER_DEFAULTS, **kw

google/cloud/logging_v2/handlers/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616

1717
from google.cloud.logging_v2.handlers.app_engine import AppEngineHandler
1818
from google.cloud.logging_v2.handlers.container_engine import ContainerEngineHandler
19+
from google.cloud.logging_v2.handlers.structured_log import StructuredLogHandler
1920
from google.cloud.logging_v2.handlers.handlers import CloudLoggingHandler
21+
from google.cloud.logging_v2.handlers.handlers import CloudLoggingFilter
2022
from google.cloud.logging_v2.handlers.handlers import setup_logging
2123

2224
__all__ = [
2325
"AppEngineHandler",
26+
"CloudLoggingFilter",
2427
"CloudLoggingHandler",
2528
"ContainerEngineHandler",
29+
"StructuredLogHandler",
2630
"setup_logging",
2731
]

google/cloud/logging_v2/handlers/_helpers.py

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import math
1818
import json
19+
import re
1920

2021
try:
2122
import flask
@@ -55,12 +56,13 @@ def get_request_data_from_flask():
5556
"""Get http_request and trace data from flask request headers.
5657
5758
Returns:
58-
Tuple[Optional[dict], Optional[str]]:
59-
Data related to the current http request and the trace_id for the
60-
request. Both fields will be None if a flask request isn't found.
59+
Tuple[Optional[dict], Optional[str], Optional[str]]:
60+
Data related to the current http request, trace_id, and span_id for
61+
the request. All fields will be None if a django request isn't
62+
found.
6163
"""
6264
if flask is None or not flask.request:
63-
return None, None
65+
return None, None, None
6466

6567
# build http_request
6668
http_request = {
@@ -73,34 +75,34 @@ def get_request_data_from_flask():
7375
"protocol": flask.request.environ.get(_PROTOCOL_HEADER),
7476
}
7577

76-
# find trace id
77-
trace_id = None
78+
# find trace id and span id
7879
header = flask.request.headers.get(_FLASK_TRACE_HEADER)
79-
if header:
80-
trace_id = header.split("/", 1)[0]
80+
trace_id, span_id = _parse_trace_span(header)
8181

82-
return http_request, trace_id
82+
return http_request, trace_id, span_id
8383

8484

8585
def get_request_data_from_django():
8686
"""Get http_request and trace data from django request headers.
8787
8888
Returns:
89-
Tuple[Optional[dict], Optional[str]]:
90-
Data related to the current http request and the trace_id for the
91-
request. Both fields will be None if a django request isn't found.
89+
Tuple[Optional[dict], Optional[str], Optional[str]]:
90+
Data related to the current http request, trace_id, and span_id for
91+
the request. All fields will be None if a django request isn't
92+
found.
9293
"""
9394
request = _get_django_request()
9495

9596
if request is None:
96-
return None, None
97+
return None, None, None
9798

9899
# convert content_length to int if it exists
99100
content_length = None
100101
try:
101102
content_length = int(request.META.get(_DJANGO_CONTENT_LENGTH))
102103
except (ValueError, TypeError):
103104
content_length = None
105+
104106
# build http_request
105107
http_request = {
106108
"requestMethod": request.method,
@@ -112,32 +114,55 @@ def get_request_data_from_django():
112114
"protocol": request.META.get(_PROTOCOL_HEADER),
113115
}
114116

115-
# find trace id
116-
trace_id = None
117+
# find trace id and span id
117118
header = request.META.get(_DJANGO_TRACE_HEADER)
118-
if header:
119-
trace_id = header.split("/", 1)[0]
119+
trace_id, span_id = _parse_trace_span(header)
120+
121+
return http_request, trace_id, span_id
120122

121-
return http_request, trace_id
123+
124+
def _parse_trace_span(header):
125+
"""Given an X_CLOUD_TRACE header, extract the trace and span ids.
126+
127+
Args:
128+
header (str): the string extracted from the X_CLOUD_TRACE header
129+
Returns:
130+
Tuple[Optional[dict], Optional[str]]:
131+
The trace_id and span_id extracted from the header
132+
Each field will be None if not found.
133+
"""
134+
trace_id = None
135+
span_id = None
136+
if header:
137+
try:
138+
split_header = header.split("/", 1)
139+
trace_id = split_header[0]
140+
header_suffix = split_header[1]
141+
# the span is the set of alphanumeric characters after the /
142+
span_id = re.findall(r"^\w+", header_suffix)[0]
143+
except IndexError:
144+
pass
145+
return trace_id, span_id
122146

123147

124148
def get_request_data():
125149
"""Helper to get http_request and trace data from supported web
126150
frameworks (currently supported: Flask and Django).
127151
128152
Returns:
129-
Tuple[Optional[dict], Optional[str]]:
130-
Data related to the current http request and the trace_id for the
131-
request. Both fields will be None if a supported web request isn't found.
153+
Tuple[Optional[dict], Optional[str], Optional[str]]:
154+
Data related to the current http request, trace_id, and span_id for
155+
the request. All fields will be None if a django request isn't
156+
found.
132157
"""
133158
checkers = (
134159
get_request_data_from_django,
135160
get_request_data_from_flask,
136161
)
137162

138163
for checker in checkers:
139-
http_request, trace_id = checker()
164+
http_request, trace_id, span_id = checker()
140165
if http_request is not None:
141-
return http_request, trace_id
166+
return http_request, trace_id, span_id
142167

143-
return None, None
168+
return None, None, None

google/cloud/logging_v2/handlers/app_engine.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def get_gae_labels(self):
9090
"""
9191
gae_labels = {}
9292

93-
_, trace_id = get_request_data()
93+
_, trace_id, _ = get_request_data()
9494
if trace_id is not None:
9595
gae_labels[_TRACE_ID_LABEL] = trace_id
9696

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

0 commit comments

Comments
 (0)