| // 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 ( |
| "io/ioutil" |
| "os" |
| "strings" |
| "sync" |
| |
| "cloud.google.com/go/compute/metadata" |
| 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 } |
| |
| var detectedResource struct { |
| pb *mrpb.MonitoredResource |
| once sync.Once |
| } |
| |
| // isAppEngine returns true for both standard and flex |
| func isAppEngine() bool { |
| _, service := os.LookupEnv("GAE_SERVICE") |
| _, version := os.LookupEnv("GAE_VERSION") |
| _, instance := os.LookupEnv("GAE_INSTANCE") |
| |
| return service && version && instance |
| } |
| |
| func detectAppEngineResource() *mrpb.MonitoredResource { |
| projectID, err := metadata.ProjectID() |
| if err != nil { |
| return nil |
| } |
| if projectID == "" { |
| projectID = os.Getenv("GOOGLE_CLOUD_PROJECT") |
| } |
| zone, err := metadata.Zone() |
| if err != nil { |
| return nil |
| } |
| |
| return &mrpb.MonitoredResource{ |
| Type: "gae_app", |
| Labels: map[string]string{ |
| "project_id": projectID, |
| "module_id": os.Getenv("GAE_SERVICE"), |
| "version_id": os.Getenv("GAE_VERSION"), |
| "instance_id": os.Getenv("GAE_INSTANCE"), |
| "runtime": os.Getenv("GAE_RUNTIME"), |
| "zone": zone, |
| }, |
| } |
| } |
| |
| func isCloudFunction() bool { |
| // Reserved envvars in older function runtimes, e.g. Node.js 8, Python 3.7 and Go 1.11. |
| _, name := os.LookupEnv("FUNCTION_NAME") |
| _, region := os.LookupEnv("FUNCTION_REGION") |
| _, entry := os.LookupEnv("ENTRY_POINT") |
| |
| // Reserved envvars in newer function runtimes. |
| _, target := os.LookupEnv("FUNCTION_TARGET") |
| _, signature := os.LookupEnv("FUNCTION_SIGNATURE_TYPE") |
| _, service := os.LookupEnv("K_SERVICE") |
| return (name && region && entry) || (target && signature && service) |
| } |
| |
| func detectCloudFunction() *mrpb.MonitoredResource { |
| projectID, err := metadata.ProjectID() |
| if err != nil { |
| return nil |
| } |
| zone, err := metadata.Zone() |
| if err != nil { |
| return nil |
| } |
| // Newer functions runtimes store name in K_SERVICE. |
| functionName, exists := os.LookupEnv("K_SERVICE") |
| if !exists { |
| functionName, _ = os.LookupEnv("FUNCTION_NAME") |
| } |
| return &mrpb.MonitoredResource{ |
| Type: "cloud_function", |
| Labels: map[string]string{ |
| "project_id": projectID, |
| "region": regionFromZone(zone), |
| "function_name": functionName, |
| }, |
| } |
| } |
| |
| func isCloudRun() bool { |
| _, config := os.LookupEnv("K_CONFIGURATION") |
| _, service := os.LookupEnv("K_SERVICE") |
| _, revision := os.LookupEnv("K_REVISION") |
| return config && service && revision |
| } |
| |
| func detectCloudRunResource() *mrpb.MonitoredResource { |
| projectID, err := metadata.ProjectID() |
| if err != nil { |
| return nil |
| } |
| zone, err := metadata.Zone() |
| if err != nil { |
| return nil |
| } |
| return &mrpb.MonitoredResource{ |
| Type: "cloud_run_revision", |
| Labels: map[string]string{ |
| "project_id": projectID, |
| "location": regionFromZone(zone), |
| "service_name": os.Getenv("K_SERVICE"), |
| "revision_name": os.Getenv("K_REVISION"), |
| "configuration_name": os.Getenv("K_CONFIGURATION"), |
| }, |
| } |
| } |
| |
| func isKubernetesEngine() bool { |
| clusterName, err := metadata.InstanceAttributeValue("cluster-name") |
| // Note: InstanceAttributeValue can return "", nil |
| if err != nil || clusterName == "" { |
| return false |
| } |
| return true |
| } |
| |
| func detectKubernetesResource() *mrpb.MonitoredResource { |
| projectID, err := metadata.ProjectID() |
| if err != nil { |
| return nil |
| } |
| zone, err := metadata.Zone() |
| if err != nil { |
| return nil |
| } |
| clusterName, err := metadata.InstanceAttributeValue("cluster-name") |
| if err != nil { |
| return nil |
| } |
| namespaceBytes, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") |
| namespaceName := "" |
| if err == nil { |
| namespaceName = string(namespaceBytes) |
| } |
| return &mrpb.MonitoredResource{ |
| Type: "k8s_container", |
| Labels: map[string]string{ |
| "cluster_name": clusterName, |
| "location": zone, |
| "project_id": projectID, |
| "pod_name": os.Getenv("HOSTNAME"), |
| "namespace_name": namespaceName, |
| // To get the `container_name` label, users need to explicitly provide it. |
| "container_name": os.Getenv("CONTAINER_NAME"), |
| }, |
| } |
| } |
| |
| func detectGCEResource() *mrpb.MonitoredResource { |
| projectID, err := metadata.ProjectID() |
| if err != nil { |
| return nil |
| } |
| id, err := metadata.InstanceID() |
| if err != nil { |
| return nil |
| } |
| zone, err := metadata.Zone() |
| if err != nil { |
| return nil |
| } |
| name, err := metadata.InstanceName() |
| if err != nil { |
| return nil |
| } |
| return &mrpb.MonitoredResource{ |
| Type: "gce_instance", |
| Labels: map[string]string{ |
| "project_id": projectID, |
| "instance_id": id, |
| "instance_name": name, |
| "zone": zone, |
| }, |
| } |
| } |
| |
| func detectResource() *mrpb.MonitoredResource { |
| detectedResource.once.Do(func() { |
| switch { |
| // AppEngine, Functions, CloudRun, Kubernetes are detected first, |
| // as metadata.OnGCE() erroneously returns true on these runtimes. |
| case isAppEngine(): |
| detectedResource.pb = detectAppEngineResource() |
| case isCloudFunction(): |
| detectedResource.pb = detectCloudFunction() |
| case isCloudRun(): |
| detectedResource.pb = detectCloudRunResource() |
| case isKubernetesEngine(): |
| detectedResource.pb = detectKubernetesResource() |
| case metadata.OnGCE(): |
| detectedResource.pb = detectGCEResource() |
| } |
| }) |
| return detectedResource.pb |
| } |
| |
| 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 regionFromZone(zone string) string { |
| cutoff := strings.LastIndex(zone, "-") |
| if cutoff > 0 { |
| return zone[:cutoff] |
| } |
| return zone |
| } |
| |
| func globalResource(projectID string) *mrpb.MonitoredResource { |
| return &mrpb.MonitoredResource{ |
| Type: "global", |
| Labels: map[string]string{ |
| "project_id": projectID, |
| }, |
| } |
| } |