Skip to content

Commit 9c5e8f0

Browse files
fix: Added environment specific labels to client library when running in Cloud Run Jobs (#877)
* fix: Added environment specific labels to client library when running in Cloud Run Jobs * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Removed unnecessary import * Changed unit tests to pytest * Renamed add_environmental_labels to add_resource_labels; cached portions of add_resource_labels * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Updated comments and _get_environmental_labels * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 96943de commit 9c5e8f0

File tree

3 files changed

+123
-26
lines changed

3 files changed

+123
-26
lines changed

google/cloud/logging_v2/handlers/_monitored_resources.py

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import functools
16+
import logging
1517
import os
1618

1719
from google.cloud.logging_v2.resource import Resource
@@ -67,6 +69,20 @@
6769
_PROJECT_NAME = "project/project-id"
6870
"""Attribute in metadata server when in GKE environment."""
6971

72+
_GAE_RESOURCE_TYPE = "gae_app"
73+
"""Resource type for App Engine environment."""
74+
75+
_CLOUD_RUN_JOB_RESOURCE_TYPE = "cloud_run_job"
76+
"""Resource type for Cloud Run Jobs."""
77+
78+
_GAE_TRACE_ID_LABEL = "appengine.googleapis.com/trace_id"
79+
"""Extra trace label to be added on App Engine environments"""
80+
81+
_CLOUD_RUN_JOBS_EXECUTION_NAME_LABEL = "run.googleapis.com/execution_name"
82+
_CLOUD_RUN_JOBS_TASK_INDEX_LABEL = "run.googleapis.com/task_index"
83+
_CLOUD_RUN_JOBS_TASK_ATTEMPT_LABEL = "run.googleapis.com/task_attempt"
84+
"""Extra labels for Cloud Run environments to be recognized by Cloud Run Jobs web UI."""
85+
7086

7187
def _create_functions_resource():
7288
"""Create a standardized Cloud Functions resource.
@@ -159,7 +175,7 @@ def _create_cloud_run_job_resource():
159175
region = retrieve_metadata_server(_REGION_ID)
160176
project = retrieve_metadata_server(_PROJECT_NAME)
161177
resource = Resource(
162-
type="cloud_run_job",
178+
type=_CLOUD_RUN_JOB_RESOURCE_TYPE,
163179
labels={
164180
"project_id": project if project else "",
165181
"job_name": os.environ.get(_CLOUD_RUN_JOB_ID, ""),
@@ -177,7 +193,7 @@ def _create_app_engine_resource():
177193
zone = retrieve_metadata_server(_ZONE_ID)
178194
project = retrieve_metadata_server(_PROJECT_NAME)
179195
resource = Resource(
180-
type="gae_app",
196+
type=_GAE_RESOURCE_TYPE,
181197
labels={
182198
"project_id": project if project else "",
183199
"module_id": os.environ.get(_GAE_SERVICE_ENV, ""),
@@ -233,3 +249,55 @@ def detect_resource(project=""):
233249
else:
234250
# use generic global resource
235251
return _create_global_resource(project)
252+
253+
254+
@functools.lru_cache(maxsize=None)
255+
def _get_environmental_labels(resource_type):
256+
"""Builds a dictionary of labels to be inserted into a LogRecord of the given resource type.
257+
This function should only build a dict of items that are consistent across multiple logging statements
258+
of the same resource type, such as environment variables. Th
259+
260+
Returns:
261+
dict:
262+
A dict representation of labels and the values of those labels
263+
"""
264+
labels = {}
265+
environ_vars = {
266+
_CLOUD_RUN_JOB_RESOURCE_TYPE: {
267+
_CLOUD_RUN_JOBS_EXECUTION_NAME_LABEL: _CLOUD_RUN_EXECUTION_ID,
268+
_CLOUD_RUN_JOBS_TASK_INDEX_LABEL: _CLOUD_RUN_TASK_INDEX,
269+
_CLOUD_RUN_JOBS_TASK_ATTEMPT_LABEL: _CLOUD_RUN_TASK_ATTEMPT,
270+
}
271+
}
272+
273+
if resource_type in environ_vars:
274+
for key, env_var in environ_vars[resource_type].items():
275+
val = os.environ.get(env_var, "")
276+
if val:
277+
labels[key] = val
278+
279+
return labels
280+
281+
282+
def add_resource_labels(resource: Resource, record: logging.LogRecord):
283+
"""Returns additional labels to be appended on to a LogRecord object based on the
284+
local environment. Defaults to an empty dictionary if none apply. This is only to be
285+
used for CloudLoggingHandler, as the structured logging daemon already does this.
286+
287+
Args:
288+
resource (google.cloud.logging.Resource): Resource based on the environment
289+
record (logging.LogRecord): A LogRecord object representing a log record
290+
Returns:
291+
Dict[str, str]: New labels to append to the labels of the LogRecord
292+
"""
293+
if not resource:
294+
return None
295+
296+
# Get environmental labels from the resource type
297+
labels = _get_environmental_labels(resource.type)
298+
299+
# Add labels from log record
300+
if resource.type == _GAE_RESOURCE_TYPE and record._trace is not None:
301+
labels[_GAE_TRACE_ID_LABEL] = record._trace
302+
303+
return labels

google/cloud/logging_v2/handlers/handlers.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
import logging
2020

2121
from google.cloud.logging_v2.handlers.transports import BackgroundThreadTransport
22-
from google.cloud.logging_v2.handlers._monitored_resources import detect_resource
22+
from google.cloud.logging_v2.handlers._monitored_resources import (
23+
detect_resource,
24+
add_resource_labels,
25+
)
2326
from google.cloud.logging_v2.handlers._helpers import get_request_data
2427

2528
DEFAULT_LOGGER_NAME = "python"
@@ -40,12 +43,6 @@
4043
"""These environments require us to remove extra handlers on setup"""
4144
_CLEAR_HANDLER_RESOURCE_TYPES = ("gae_app", "cloud_function")
4245

43-
"""Extra trace label to be added on App Engine environments"""
44-
_GAE_TRACE_ID_LABEL = "appengine.googleapis.com/trace_id"
45-
46-
"""Resource name for App Engine environments"""
47-
_GAE_RESOURCE_TYPE = "gae_app"
48-
4946

5047
class CloudLoggingFilter(logging.Filter):
5148
"""Python standard ``logging`` Filter class to add Cloud Logging
@@ -206,9 +203,8 @@ def emit(self, record):
206203
labels = record._labels
207204
message = _format_and_parse_message(record, self)
208205

209-
if resource.type == _GAE_RESOURCE_TYPE and record._trace is not None:
210-
# add GAE-specific label
211-
labels = {_GAE_TRACE_ID_LABEL: record._trace, **(labels or {})}
206+
labels = {**add_resource_labels(resource, record), **(labels or {})} or None
207+
212208
# send off request
213209
self.transport.send(
214210
record,

tests/unit/handlers/test__monitored_resources.py

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,25 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import pytest
1516
import unittest
1617

18+
import logging
1719
import mock
1820
import os
1921
import functools
2022

21-
from google.cloud.logging_v2.handlers._monitored_resources import (
22-
_create_functions_resource,
23-
)
2423
from google.cloud.logging_v2.handlers._monitored_resources import (
2524
_create_app_engine_resource,
26-
)
27-
from google.cloud.logging_v2.handlers._monitored_resources import (
25+
_create_functions_resource,
2826
_create_kubernetes_resource,
29-
)
30-
from google.cloud.logging_v2.handlers._monitored_resources import (
3127
_create_cloud_run_service_resource,
32-
)
33-
from google.cloud.logging_v2.handlers._monitored_resources import (
3428
_create_cloud_run_job_resource,
35-
)
36-
from google.cloud.logging_v2.handlers._monitored_resources import (
3729
_create_compute_resource,
38-
)
39-
from google.cloud.logging_v2.handlers._monitored_resources import (
4030
_create_global_resource,
31+
detect_resource,
32+
add_resource_labels,
4133
)
42-
from google.cloud.logging_v2.handlers._monitored_resources import detect_resource
4334
from google.cloud.logging_v2.handlers import _monitored_resources
4435
from google.cloud.logging_v2.resource import Resource
4536

@@ -353,3 +344,45 @@ def test_detect_partial_data(self):
353344
# project id not returned from metadata serve
354345
# should be empty string
355346
self.assertEqual(resource.labels["project_id"], "")
347+
348+
349+
@pytest.mark.parametrize(
350+
"resource_type,os_environ,record_attrs,expected_labels",
351+
[
352+
(
353+
_monitored_resources._GAE_RESOURCE_TYPE,
354+
{},
355+
{"_trace": "trace_id"},
356+
{_monitored_resources._GAE_TRACE_ID_LABEL: "trace_id"},
357+
),
358+
(
359+
_monitored_resources._CLOUD_RUN_JOB_RESOURCE_TYPE,
360+
{
361+
_monitored_resources._CLOUD_RUN_EXECUTION_ID: "test_job_12345",
362+
_monitored_resources._CLOUD_RUN_TASK_INDEX: "1",
363+
_monitored_resources._CLOUD_RUN_TASK_ATTEMPT: "12",
364+
},
365+
{},
366+
{
367+
_monitored_resources._CLOUD_RUN_JOBS_EXECUTION_NAME_LABEL: "test_job_12345",
368+
_monitored_resources._CLOUD_RUN_JOBS_TASK_INDEX_LABEL: "1",
369+
_monitored_resources._CLOUD_RUN_JOBS_TASK_ATTEMPT_LABEL: "12",
370+
},
371+
),
372+
("global", {}, {}, {}),
373+
],
374+
)
375+
def test_add_resource_labels(resource_type, os_environ, record_attrs, expected_labels):
376+
os.environ.clear()
377+
record = logging.LogRecord("logname", None, None, None, "test", None, None)
378+
379+
resource = Resource(type=resource_type, labels={})
380+
381+
for attr, val in record_attrs.items():
382+
setattr(record, attr, val)
383+
384+
os.environ.update(os_environ)
385+
386+
labels = add_resource_labels(resource, record)
387+
388+
assert expected_labels == labels

0 commit comments

Comments
 (0)