// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package logging import ( "runtime" "strings" "sync" "cloud.google.com/go/logging/internal" mrpb "google.golang.org/genproto/googleapis/api/monitoredres" ) // CommonResource sets the monitored resource associated with all log entries // written from a Logger. If not provided, the resource is automatically // detected based on the running environment (on GCE, GCR, GCF and GAE Standard only). // This value can be overridden per-entry by setting an Entry's Resource field. func CommonResource(r *mrpb.MonitoredResource) LoggerOption { return commonResource{r} } type commonResource struct{ *mrpb.MonitoredResource } func (r commonResource) set(l *Logger) { l.commonResource = r.MonitoredResource } type resource struct { pb *mrpb.MonitoredResource attrs internal.ResourceAttributesGetter once *sync.Once } var detectedResource = &resource{ attrs: internal.ResourceAttributes(), once: new(sync.Once), } func (r *resource) metadataProjectID() string { return r.attrs.Metadata("project/project-id") } func (r *resource) metadataZone() string { zone := r.attrs.Metadata("instance/zone") if zone != "" { return zone[strings.LastIndex(zone, "/")+1:] } return "" } func (r *resource) metadataRegion() string { region := r.attrs.Metadata("instance/region") if region != "" { return region[strings.LastIndex(region, "/")+1:] } return "" } // isMetadataActive queries valid response on "/computeMetadata/v1/" URL func (r *resource) isMetadataActive() bool { data := r.attrs.Metadata("") return data != "" } // isAppEngine returns true for both standard and flex func (r *resource) isAppEngine() bool { service := r.attrs.EnvVar("GAE_SERVICE") version := r.attrs.EnvVar("GAE_VERSION") instance := r.attrs.EnvVar("GAE_INSTANCE") return service != "" && version != "" && instance != "" } func detectAppEngineResource() *mrpb.MonitoredResource { projectID := detectedResource.metadataProjectID() if projectID == "" { projectID = detectedResource.attrs.EnvVar("GOOGLE_CLOUD_PROJECT") } if projectID == "" { return nil } zone := detectedResource.metadataZone() service := detectedResource.attrs.EnvVar("GAE_SERVICE") version := detectedResource.attrs.EnvVar("GAE_VERSION") return &mrpb.MonitoredResource{ Type: "gae_app", Labels: map[string]string{ "project_id": projectID, "module_id": service, "version_id": version, "zone": zone, }, } } func (r *resource) isCloudFunction() bool { target := r.attrs.EnvVar("FUNCTION_TARGET") signature := r.attrs.EnvVar("FUNCTION_SIGNATURE_TYPE") // note that this envvar is also present in Cloud Run environments service := r.attrs.EnvVar("K_SERVICE") return target != "" && signature != "" && service != "" } func detectCloudFunction() *mrpb.MonitoredResource { projectID := detectedResource.metadataProjectID() if projectID == "" { return nil } region := detectedResource.metadataRegion() functionName := detectedResource.attrs.EnvVar("K_SERVICE") return &mrpb.MonitoredResource{ Type: "cloud_function", Labels: map[string]string{ "project_id": projectID, "region": region, "function_name": functionName, }, } } func (r *resource) isCloudRunService() bool { config := r.attrs.EnvVar("K_CONFIGURATION") // note that this envvar is also present in Cloud Function environments service := r.attrs.EnvVar("K_SERVICE") revision := r.attrs.EnvVar("K_REVISION") return config != "" && service != "" && revision != "" } func (r *resource) isCloudRunJob() bool { if r.attrs.EnvVar("CLOUD_RUN_JOB") == "" { return false } if r.attrs.EnvVar("CLOUD_RUN_EXECUTION") == "" { return false } if r.attrs.EnvVar("CLOUD_RUN_TASK_INDEX") == "" { return false } if r.attrs.EnvVar("CLOUD_RUN_TASK_ATTEMPT") == "" { return false } return true } func detectCloudRunServiceResource() *mrpb.MonitoredResource { projectID := detectedResource.metadataProjectID() if projectID == "" { return nil } region := detectedResource.metadataRegion() config := detectedResource.attrs.EnvVar("K_CONFIGURATION") service := detectedResource.attrs.EnvVar("K_SERVICE") revision := detectedResource.attrs.EnvVar("K_REVISION") return &mrpb.MonitoredResource{ Type: "cloud_run_revision", Labels: map[string]string{ "project_id": projectID, "location": region, "service_name": service, "revision_name": revision, "configuration_name": config, }, } } func detectCloudRunJobResource() *mrpb.MonitoredResource { projectID := detectedResource.metadataProjectID() if projectID == "" { return nil } region := detectedResource.metadataRegion() job := detectedResource.attrs.EnvVar("CLOUD_RUN_JOB") return &mrpb.MonitoredResource{ Type: "cloud_run_job", Labels: map[string]string{ "project_id": projectID, "location": region, "job_name": job, }, } } func (r *resource) isKubernetesEngine() bool { clusterName := r.attrs.Metadata("instance/attributes/cluster-name") if clusterName == "" { return false } return true } func detectKubernetesResource() *mrpb.MonitoredResource { projectID := detectedResource.metadataProjectID() if projectID == "" { return nil } clusterName := detectedResource.attrs.Metadata("instance/attributes/cluster-name") clusterLocation := detectedResource.attrs.Metadata("instance/attributes/cluster-location") namespaceName := detectedResource.attrs.ReadAll("/var/run/secrets/kubernetes.io/serviceaccount/namespace") if namespaceName == "" { // if automountServiceAccountToken is disabled allow to customize // the namespace via environment namespaceName = detectedResource.attrs.EnvVar("NAMESPACE_NAME") } // note: if deployment customizes hostname, HOSTNAME envvar will have invalid content podName := detectedResource.attrs.EnvVar("HOSTNAME") // there is no way to derive container name from within container; use custom envvar if available containerName := detectedResource.attrs.EnvVar("CONTAINER_NAME") return &mrpb.MonitoredResource{ Type: "k8s_container", Labels: map[string]string{ "cluster_name": clusterName, "location": clusterLocation, "project_id": projectID, "pod_name": podName, "namespace_name": namespaceName, "container_name": containerName, }, } } func (r *resource) isComputeEngine() bool { preempted := r.attrs.Metadata("instance/preempted") platform := r.attrs.Metadata("instance/cpu-platform") appBucket := r.attrs.Metadata("instance/attributes/gae_app_bucket") return preempted != "" && platform != "" && appBucket == "" } func detectComputeEngineResource() *mrpb.MonitoredResource { projectID := detectedResource.metadataProjectID() if projectID == "" { return nil } id := detectedResource.attrs.Metadata("instance/id") zone := detectedResource.metadataZone() return &mrpb.MonitoredResource{ Type: "gce_instance", Labels: map[string]string{ "project_id": projectID, "instance_id": id, "zone": zone, }, } } func detectResource() *mrpb.MonitoredResource { detectedResource.once.Do(func() { if detectedResource.isMetadataActive() { name := systemProductName() switch { case name == "Google App Engine", detectedResource.isAppEngine(): detectedResource.pb = detectAppEngineResource() case name == "Google Cloud Functions", detectedResource.isCloudFunction(): detectedResource.pb = detectCloudFunction() // cannot use name validation for Cloud Run resources because // both of them set product name to "Google Cloud Run" case detectedResource.isCloudRunService(): detectedResource.pb = detectCloudRunServiceResource() case detectedResource.isCloudRunJob(): detectedResource.pb = detectCloudRunJobResource() // cannot use name validation for GKE and GCE because // both of them set product name to "Google Compute Engine" case detectedResource.isKubernetesEngine(): detectedResource.pb = detectKubernetesResource() case detectedResource.isComputeEngine(): detectedResource.pb = detectComputeEngineResource() } } }) return detectedResource.pb } // systemProductName reads resource type on the Linux-based environments such as // Cloud Functions, Cloud Run, GKE, GCE, GAE, etc. func systemProductName() string { if runtime.GOOS != "linux" { // We don't have any non-Linux clues available, at least yet. return "" } slurp := detectedResource.attrs.ReadAll("/sys/class/dmi/id/product_name") return strings.TrimSpace(slurp) } var resourceInfo = map[string]struct{ rtype, label string }{ "organizations": {"organization", "organization_id"}, "folders": {"folder", "folder_id"}, "projects": {"project", "project_id"}, "billingAccounts": {"billing_account", "account_id"}, } func monitoredResource(parent string) *mrpb.MonitoredResource { parts := strings.SplitN(parent, "/", 2) if len(parts) != 2 { return globalResource(parent) } info, ok := resourceInfo[parts[0]] if !ok { return globalResource(parts[1]) } return &mrpb.MonitoredResource{ Type: info.rtype, Labels: map[string]string{info.label: parts[1]}, } } func globalResource(projectID string) *mrpb.MonitoredResource { return &mrpb.MonitoredResource{ Type: "global", Labels: map[string]string{ "project_id": projectID, }, } }