Skip to content

Commit 4eda681

Browse files
feat: detect monitored resources on all GCP environments (#200)
1 parent 22f836a commit 4eda681

File tree

8 files changed

+493
-42
lines changed

8 files changed

+493
-42
lines changed

google/cloud/logging_v2/client.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
from google.cloud.client import ClientWithProject
3030
from google.cloud.environment_vars import DISABLE_GRPC
3131
from google.cloud.logging_v2._helpers import _add_defaults_to_filter
32-
from google.cloud.logging_v2._helpers import retrieve_metadata_server
3332
from google.cloud.logging_v2._http import Connection
3433
from google.cloud.logging_v2._http import _LoggingAPI as JSONLoggingAPI
3534
from google.cloud.logging_v2._http import _MetricsAPI as JSONMetricsAPI
@@ -39,6 +38,9 @@
3938
from google.cloud.logging_v2.handlers import ContainerEngineHandler
4039
from google.cloud.logging_v2.handlers import setup_logging
4140
from google.cloud.logging_v2.handlers.handlers import EXCLUDED_LOGGER_DEFAULTS
41+
from google.cloud.logging_v2.resource import Resource
42+
from google.cloud.logging_v2.handlers._monitored_resources import detect_resource
43+
4244

4345
from google.cloud.logging_v2.logger import Logger
4446
from google.cloud.logging_v2.metric import Metric
@@ -48,14 +50,8 @@
4850
_DISABLE_GRPC = os.getenv(DISABLE_GRPC, False)
4951
_USE_GRPC = _HAVE_GRPC and not _DISABLE_GRPC
5052

51-
_APPENGINE_FLEXIBLE_ENV_VM = "GAE_APPENGINE_HOSTNAME"
52-
"""Environment variable set in App Engine when vm:true is set."""
53-
54-
_APPENGINE_INSTANCE_ID = "GAE_INSTANCE"
55-
"""Environment variable set in App Engine standard and flexible environment."""
56-
57-
_GKE_CLUSTER_NAME = "instance/attributes/cluster-name"
58-
"""Attribute in metadata server when in GKE environment."""
53+
_GAE_RESOURCE_TYPE = "gae_app"
54+
_GKE_RESOURCE_TYPE = "k8s_container"
5955

6056

6157
class Client(ClientWithProject):
@@ -348,17 +344,20 @@ def get_default_handler(self, **kw):
348344
Returns:
349345
logging.Handler: The default log handler based on the environment
350346
"""
351-
gke_cluster_name = retrieve_metadata_server(_GKE_CLUSTER_NAME)
347+
monitored_resource = kw.pop("resource", detect_resource(self.project))
352348

353349
if (
354-
_APPENGINE_FLEXIBLE_ENV_VM in os.environ
355-
or _APPENGINE_INSTANCE_ID in os.environ
350+
isinstance(monitored_resource, Resource)
351+
and monitored_resource.type == _GAE_RESOURCE_TYPE
356352
):
357353
return AppEngineHandler(self, **kw)
358-
elif gke_cluster_name is not None:
354+
elif (
355+
isinstance(monitored_resource, Resource)
356+
and monitored_resource.type == _GKE_RESOURCE_TYPE
357+
):
359358
return ContainerEngineHandler(**kw)
360359
else:
361-
return CloudLoggingHandler(self, **kw)
360+
return CloudLoggingHandler(self, resource=monitored_resource, **kw)
362361

363362
def setup_logging(
364363
self, *, log_level=logging.INFO, excluded_loggers=EXCLUDED_LOGGER_DEFAULTS, **kw
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# Copyright 2021 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
17+
from google.cloud.logging_v2.resource import Resource
18+
from google.cloud.logging_v2._helpers import retrieve_metadata_server
19+
20+
_GAE_SERVICE_ENV = "GAE_SERVICE"
21+
_GAE_VERSION_ENV = "GAE_VERSION"
22+
_GAE_INSTANCE_ENV = "GAE_INSTANCE"
23+
_GAE_ENV_VARS = [_GAE_SERVICE_ENV, _GAE_VERSION_ENV, _GAE_INSTANCE_ENV]
24+
"""Environment variables set in App Engine environment."""
25+
26+
_CLOUD_RUN_SERVICE_ID = "K_SERVICE"
27+
_CLOUD_RUN_REVISION_ID = "K_REVISION"
28+
_CLOUD_RUN_CONFIGURATION_ID = "K_CONFIGURATION"
29+
_CLOUD_RUN_ENV_VARS = [
30+
_CLOUD_RUN_SERVICE_ID,
31+
_CLOUD_RUN_REVISION_ID,
32+
_CLOUD_RUN_CONFIGURATION_ID,
33+
]
34+
"""Environment variables set in Cloud Run environment."""
35+
36+
_FUNCTION_TARGET = "FUNCTION_TARGET"
37+
_FUNCTION_SIGNATURE = "FUNCTION_SIGNATURE_TYPE"
38+
_FUNCTION_NAME = "FUNCTION_NAME"
39+
_FUNCTION_REGION = "FUNCTION_REGION"
40+
_FUNCTION_ENTRY = "ENTRY_POINT"
41+
_FUNCTION_ENV_VARS = [_FUNCTION_TARGET, _FUNCTION_SIGNATURE, _CLOUD_RUN_SERVICE_ID]
42+
_LEGACY_FUNCTION_ENV_VARS = [_FUNCTION_NAME, _FUNCTION_REGION, _FUNCTION_ENTRY]
43+
"""Environment variables set in Cloud Functions environments."""
44+
45+
46+
_REGION_ID = "instance/region"
47+
_ZONE_ID = "instance/zone"
48+
_GCE_INSTANCE_ID = "instance/id"
49+
"""Attribute in metadata server for compute region and instance."""
50+
51+
_GKE_CLUSTER_NAME = "instance/attributes/cluster-name"
52+
"""Attribute in metadata server when in GKE environment."""
53+
54+
55+
def _create_functions_resource(project):
56+
"""Create a standardized Cloud Functions resource.
57+
Args:
58+
project (str): The project ID to pass on to the resource
59+
Returns:
60+
google.cloud.logging.Resource
61+
"""
62+
region = retrieve_metadata_server(_REGION_ID)
63+
if _FUNCTION_NAME in os.environ:
64+
function_name = os.environ.get(_FUNCTION_NAME)
65+
elif _CLOUD_RUN_SERVICE_ID in os.environ:
66+
function_name = os.environ.get(_CLOUD_RUN_SERVICE_ID)
67+
else:
68+
function_name = ""
69+
resource = Resource(
70+
type="cloud_function",
71+
labels={
72+
"project_id": project,
73+
"function_name": function_name,
74+
"region": region if region else "",
75+
},
76+
)
77+
return resource
78+
79+
80+
def _create_kubernetes_resource(project):
81+
"""Create a standardized Kubernetes resource.
82+
Args:
83+
project (str): The project ID to pass on to the resource
84+
Returns:
85+
google.cloud.logging.Resource
86+
"""
87+
zone = retrieve_metadata_server(_ZONE_ID)
88+
cluster_name = retrieve_metadata_server(_GKE_CLUSTER_NAME)
89+
90+
resource = Resource(
91+
type="k8s_container",
92+
labels={
93+
"project_id": project,
94+
"location": zone if zone else "",
95+
"cluster_name": cluster_name if cluster_name else "",
96+
},
97+
)
98+
return resource
99+
100+
101+
def _create_compute_resource(project):
102+
"""Create a standardized Compute Engine resource.
103+
Args:
104+
project (str): The project ID to pass on to the resource
105+
Returns:
106+
google.cloud.logging.Resource
107+
"""
108+
instance = retrieve_metadata_server(_GCE_INSTANCE_ID)
109+
zone = retrieve_metadata_server(_ZONE_ID)
110+
resource = Resource(
111+
type="gce_instance",
112+
labels={
113+
"project_id": project,
114+
"instance_id": instance if instance else "",
115+
"zone": zone if zone else "",
116+
},
117+
)
118+
return resource
119+
120+
121+
def _create_cloud_run_resource(project):
122+
"""Create a standardized Cloud Run resource.
123+
Args:
124+
project (str): The project ID to pass on to the resource
125+
Returns:
126+
google.cloud.logging.Resource
127+
"""
128+
region = retrieve_metadata_server(_REGION_ID)
129+
resource = Resource(
130+
type="cloud_run_revision",
131+
labels={
132+
"project_id": project,
133+
"service_name": os.environ.get(_CLOUD_RUN_SERVICE_ID, ""),
134+
"revision_name": os.environ.get(_CLOUD_RUN_REVISION_ID, ""),
135+
"location": region if region else "",
136+
"configuration_name": os.environ.get(_CLOUD_RUN_CONFIGURATION_ID, ""),
137+
},
138+
)
139+
return resource
140+
141+
142+
def _create_app_engine_resource(project):
143+
"""Create a standardized App Engine resource.
144+
Args:
145+
project (str): The project ID to pass on to the resource
146+
Returns:
147+
google.cloud.logging.Resource
148+
"""
149+
zone = retrieve_metadata_server(_ZONE_ID)
150+
resource = Resource(
151+
type="gae_app",
152+
labels={
153+
"project_id": project,
154+
"module_id": os.environ.get(_GAE_SERVICE_ENV, ""),
155+
"version_id": os.environ.get(_GAE_VERSION_ENV, ""),
156+
"zone": zone if zone else "",
157+
},
158+
)
159+
return resource
160+
161+
162+
def _create_global_resource(project):
163+
return Resource(type="global", labels={"project_id": project})
164+
165+
166+
def detect_resource(project):
167+
"""Return the default monitored resource based on the local environment.
168+
Args:
169+
project (str): The project ID to pass on to the resource
170+
Returns:
171+
google.cloud.logging.Resource: The default resource based on the environment
172+
"""
173+
gke_cluster_name = retrieve_metadata_server(_GKE_CLUSTER_NAME)
174+
gce_instance_name = retrieve_metadata_server(_GCE_INSTANCE_ID)
175+
176+
if all([env in os.environ for env in _GAE_ENV_VARS]):
177+
# App Engine Flex or Standard
178+
return _create_app_engine_resource(project)
179+
elif gke_cluster_name is not None:
180+
# Kubernetes Engine
181+
return _create_kubernetes_resource(project)
182+
elif all([env in os.environ for env in _LEGACY_FUNCTION_ENV_VARS]) or all(
183+
[env in os.environ for env in _FUNCTION_ENV_VARS]
184+
):
185+
# Cloud Functions
186+
return _create_functions_resource(project)
187+
elif all([env in os.environ for env in _CLOUD_RUN_ENV_VARS]):
188+
# Cloud Run
189+
return _create_cloud_run_resource(project)
190+
elif gce_instance_name is not None:
191+
# Compute Engine
192+
return _create_compute_resource(project)
193+
else:
194+
# use generic global resource
195+
return _create_global_resource(project)

google/cloud/logging_v2/handlers/app_engine.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222
import os
2323

2424
from google.cloud.logging_v2.handlers._helpers import get_request_data
25+
from google.cloud.logging_v2.handlers._monitored_resources import (
26+
_create_app_engine_resource,
27+
)
2528
from google.cloud.logging_v2.handlers.transports import BackgroundThreadTransport
26-
from google.cloud.logging_v2.resource import Resource
2729

2830
_DEFAULT_GAE_LOGGER_NAME = "app"
2931

@@ -75,15 +77,7 @@ def get_gae_resource(self):
7577
Returns:
7678
google.cloud.logging_v2.resource.Resource: Monitored resource for GAE.
7779
"""
78-
gae_resource = Resource(
79-
type="gae_app",
80-
labels={
81-
"project_id": self.project_id,
82-
"module_id": self.module_id,
83-
"version_id": self.version_id,
84-
},
85-
)
86-
return gae_resource
80+
return _create_app_engine_resource(self.project_id)
8781

8882
def get_gae_labels(self):
8983
"""Return the labels for GAE app.

google/cloud/logging_v2/handlers/handlers.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import logging
1818

1919
from google.cloud.logging_v2.handlers.transports import BackgroundThreadTransport
20-
from google.cloud.logging_v2.logger import _GLOBAL_RESOURCE
20+
from google.cloud.logging_v2.handlers._monitored_resources import detect_resource
2121

2222
DEFAULT_LOGGER_NAME = "python"
2323

@@ -59,7 +59,7 @@ def __init__(
5959
*,
6060
name=DEFAULT_LOGGER_NAME,
6161
transport=BackgroundThreadTransport,
62-
resource=_GLOBAL_RESOURCE,
62+
resource=None,
6363
labels=None,
6464
stream=None,
6565
):
@@ -78,12 +78,15 @@ def __init__(
7878
:class:`.BackgroundThreadTransport`. The other
7979
option is :class:`.SyncTransport`.
8080
resource (~logging_v2.resource.Resource):
81-
Resource for this Handler. Defaults to ``GLOBAL_RESOURCE``.
81+
Resource for this Handler. If not given, will be inferred from the environment.
8282
labels (Optional[dict]): Monitored resource of the entry, defaults
8383
to the global resource type.
8484
stream (Optional[IO]): Stream to be used by the handler.
8585
"""
8686
super(CloudLoggingHandler, self).__init__(stream)
87+
if not resource:
88+
# infer the correct monitored resource from the local environment
89+
resource = detect_resource(client.project)
8790
self.name = name
8891
self.client = client
8992
self.transport = transport(client, name)

0 commit comments

Comments
 (0)