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
15import functools
16import logging
17import os
18
19from google.cloud.logging_v2.resource import Resource
20from google.cloud.logging_v2._helpers import retrieve_metadata_server
21
22_GAE_SERVICE_ENV = "GAE_SERVICE"
23_GAE_VERSION_ENV = "GAE_VERSION"
24_GAE_INSTANCE_ENV = "GAE_INSTANCE"
25_GAE_ENV_VARS = [_GAE_SERVICE_ENV, _GAE_VERSION_ENV, _GAE_INSTANCE_ENV]
26"""Environment variables set in App Engine environment."""
27
28_CLOUD_RUN_SERVICE_ID = "K_SERVICE"
29_CLOUD_RUN_REVISION_ID = "K_REVISION"
30_CLOUD_RUN_CONFIGURATION_ID = "K_CONFIGURATION"
31_CLOUD_RUN_SERVICE_ENV_VARS = [
32 _CLOUD_RUN_SERVICE_ID,
33 _CLOUD_RUN_REVISION_ID,
34 _CLOUD_RUN_CONFIGURATION_ID,
35]
36_CLOUD_RUN_JOB_ID = "CLOUD_RUN_JOB"
37_CLOUD_RUN_EXECUTION_ID = "CLOUD_RUN_EXECUTION"
38_CLOUD_RUN_TASK_INDEX = "CLOUD_RUN_TASK_INDEX"
39_CLOUD_RUN_TASK_ATTEMPT = "CLOUD_RUN_TASK_ATTEMPT"
40_CLOUD_RUN_JOB_ENV_VARS = [
41 _CLOUD_RUN_JOB_ID,
42 _CLOUD_RUN_EXECUTION_ID,
43 _CLOUD_RUN_TASK_INDEX,
44 _CLOUD_RUN_TASK_ATTEMPT,
45]
46"""Environment variables set in Cloud Run environment."""
47
48_FUNCTION_TARGET = "FUNCTION_TARGET"
49_FUNCTION_SIGNATURE = "FUNCTION_SIGNATURE_TYPE"
50_FUNCTION_NAME = "FUNCTION_NAME"
51_FUNCTION_REGION = "FUNCTION_REGION"
52_FUNCTION_ENTRY = "ENTRY_POINT"
53_FUNCTION_ENV_VARS = [_FUNCTION_TARGET, _FUNCTION_SIGNATURE, _CLOUD_RUN_SERVICE_ID]
54_LEGACY_FUNCTION_ENV_VARS = [_FUNCTION_NAME, _FUNCTION_REGION, _FUNCTION_ENTRY]
55"""Environment variables set in Cloud Functions environments."""
56
57
58_REGION_ID = "instance/region"
59_ZONE_ID = "instance/zone"
60_GCE_INSTANCE_ID = "instance/id"
61"""Attribute in metadata server for compute region and instance."""
62
63_GKE_CLUSTER_NAME = "instance/attributes/cluster-name"
64"""Attribute in metadata server when in GKE environment."""
65
66_GKE_CLUSTER_LOCATION = "instance/attributes/cluster-location"
67"""Attribute in metadata server when in GKE environment."""
68
69_PROJECT_NAME = "project/project-id"
70"""Attribute in metadata server when in GKE environment."""
71
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
86
87def _create_functions_resource():
88 """Create a standardized Cloud Functions resource.
89 Returns:
90 google.cloud.logging.Resource
91 """
92 project = retrieve_metadata_server(_PROJECT_NAME)
93 region = retrieve_metadata_server(_REGION_ID)
94 if _FUNCTION_NAME in os.environ:
95 function_name = os.environ.get(_FUNCTION_NAME)
96 elif _CLOUD_RUN_SERVICE_ID in os.environ:
97 function_name = os.environ.get(_CLOUD_RUN_SERVICE_ID)
98 else:
99 function_name = ""
100 resource = Resource(
101 type="cloud_function",
102 labels={
103 "project_id": project if project else "",
104 "function_name": function_name if function_name else "",
105 "region": region.split("/")[-1] if region else "",
106 },
107 )
108 return resource
109
110
111def _create_kubernetes_resource():
112 """Create a standardized Kubernetes resource.
113 Returns:
114 google.cloud.logging.Resource
115 """
116 location = retrieve_metadata_server(_GKE_CLUSTER_LOCATION)
117 cluster_name = retrieve_metadata_server(_GKE_CLUSTER_NAME)
118 project = retrieve_metadata_server(_PROJECT_NAME)
119
120 resource = Resource(
121 type="k8s_container",
122 labels={
123 "project_id": project if project else "",
124 "location": location if location else "",
125 "cluster_name": cluster_name if cluster_name else "",
126 },
127 )
128 return resource
129
130
131def _create_compute_resource():
132 """Create a standardized Compute Engine resource.
133 Returns:
134 google.cloud.logging.Resource
135 """
136 instance = retrieve_metadata_server(_GCE_INSTANCE_ID)
137 zone = retrieve_metadata_server(_ZONE_ID)
138 project = retrieve_metadata_server(_PROJECT_NAME)
139 resource = Resource(
140 type="gce_instance",
141 labels={
142 "project_id": project if project else "",
143 "instance_id": instance if instance else "",
144 "zone": zone if zone else "",
145 },
146 )
147 return resource
148
149
150def _create_cloud_run_service_resource():
151 """Create a standardized Cloud Run service resource.
152 Returns:
153 google.cloud.logging.Resource
154 """
155 region = retrieve_metadata_server(_REGION_ID)
156 project = retrieve_metadata_server(_PROJECT_NAME)
157 resource = Resource(
158 type="cloud_run_revision",
159 labels={
160 "project_id": project if project else "",
161 "service_name": os.environ.get(_CLOUD_RUN_SERVICE_ID, ""),
162 "revision_name": os.environ.get(_CLOUD_RUN_REVISION_ID, ""),
163 "location": region.split("/")[-1] if region else "",
164 "configuration_name": os.environ.get(_CLOUD_RUN_CONFIGURATION_ID, ""),
165 },
166 )
167 return resource
168
169
170def _create_cloud_run_job_resource():
171 """Create a standardized Cloud Run job resource.
172 Returns:
173 google.cloud.logging.Resource
174 """
175 region = retrieve_metadata_server(_REGION_ID)
176 project = retrieve_metadata_server(_PROJECT_NAME)
177 resource = Resource(
178 type=_CLOUD_RUN_JOB_RESOURCE_TYPE,
179 labels={
180 "project_id": project if project else "",
181 "job_name": os.environ.get(_CLOUD_RUN_JOB_ID, ""),
182 "location": region.split("/")[-1] if region else "",
183 },
184 )
185 return resource
186
187
188def _create_app_engine_resource():
189 """Create a standardized App Engine resource.
190 Returns:
191 google.cloud.logging.Resource
192 """
193 zone = retrieve_metadata_server(_ZONE_ID)
194 project = retrieve_metadata_server(_PROJECT_NAME)
195 resource = Resource(
196 type=_GAE_RESOURCE_TYPE,
197 labels={
198 "project_id": project if project else "",
199 "module_id": os.environ.get(_GAE_SERVICE_ENV, ""),
200 "version_id": os.environ.get(_GAE_VERSION_ENV, ""),
201 "zone": zone if zone else "",
202 },
203 )
204 return resource
205
206
207def _create_global_resource(project):
208 """Create a global resource.
209 Args:
210 project (str): The project ID to pass on to the resource
211 Returns:
212 google.cloud.logging.Resource
213 """
214 return Resource(type="global", labels={"project_id": project if project else ""})
215
216
217def detect_resource(project=""):
218 """Return the default monitored resource based on the local environment.
219 If GCP resource not found, defaults to `global`.
220
221 Args:
222 project (str): The project ID to pass on to the resource (if needed)
223 Returns:
224 google.cloud.logging.Resource: The default resource based on the environment
225 """
226 gke_cluster_name = retrieve_metadata_server(_GKE_CLUSTER_NAME)
227 gce_instance_name = retrieve_metadata_server(_GCE_INSTANCE_ID)
228
229 if all([env in os.environ for env in _GAE_ENV_VARS]):
230 # App Engine Flex or Standard
231 return _create_app_engine_resource()
232 elif gke_cluster_name is not None:
233 # Kubernetes Engine
234 return _create_kubernetes_resource()
235 elif all([env in os.environ for env in _LEGACY_FUNCTION_ENV_VARS]) or all(
236 [env in os.environ for env in _FUNCTION_ENV_VARS]
237 ):
238 # Cloud Functions
239 return _create_functions_resource()
240 elif all([env in os.environ for env in _CLOUD_RUN_SERVICE_ENV_VARS]):
241 # Cloud Run
242 return _create_cloud_run_service_resource()
243 elif all([env in os.environ for env in _CLOUD_RUN_JOB_ENV_VARS]):
244 # Cloud Run
245 return _create_cloud_run_job_resource()
246 elif gce_instance_name is not None:
247 # Compute Engine
248 return _create_compute_resource()
249 else:
250 # use generic global resource
251 return _create_global_resource(project)
252
253
254@functools.lru_cache(maxsize=None)
255def _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
282def 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