...

Source file src/cloud.google.com/go/logging/resource.go

Documentation: cloud.google.com/go/logging

     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  package logging
    16  
    17  import (
    18  	"runtime"
    19  	"strings"
    20  	"sync"
    21  
    22  	"cloud.google.com/go/logging/internal"
    23  	mrpb "google.golang.org/genproto/googleapis/api/monitoredres"
    24  )
    25  
    26  // CommonResource sets the monitored resource associated with all log entries
    27  // written from a Logger. If not provided, the resource is automatically
    28  // detected based on the running environment (on GCE, GCR, GCF and GAE Standard only).
    29  // This value can be overridden per-entry by setting an Entry's Resource field.
    30  func CommonResource(r *mrpb.MonitoredResource) LoggerOption { return commonResource{r} }
    31  
    32  type commonResource struct{ *mrpb.MonitoredResource }
    33  
    34  func (r commonResource) set(l *Logger) { l.commonResource = r.MonitoredResource }
    35  
    36  type resource struct {
    37  	pb    *mrpb.MonitoredResource
    38  	attrs internal.ResourceAttributesGetter
    39  	once  *sync.Once
    40  }
    41  
    42  var detectedResource = &resource{
    43  	attrs: internal.ResourceAttributes(),
    44  	once:  new(sync.Once),
    45  }
    46  
    47  func (r *resource) metadataProjectID() string {
    48  	return r.attrs.Metadata("project/project-id")
    49  }
    50  
    51  func (r *resource) metadataZone() string {
    52  	zone := r.attrs.Metadata("instance/zone")
    53  	if zone != "" {
    54  		return zone[strings.LastIndex(zone, "/")+1:]
    55  	}
    56  	return ""
    57  }
    58  
    59  func (r *resource) metadataRegion() string {
    60  	region := r.attrs.Metadata("instance/region")
    61  	if region != "" {
    62  		return region[strings.LastIndex(region, "/")+1:]
    63  	}
    64  	return ""
    65  }
    66  
    67  // isMetadataActive queries valid response on "/computeMetadata/v1/" URL
    68  func (r *resource) isMetadataActive() bool {
    69  	data := r.attrs.Metadata("")
    70  	return data != ""
    71  }
    72  
    73  // isAppEngine returns true for both standard and flex
    74  func (r *resource) isAppEngine() bool {
    75  	service := r.attrs.EnvVar("GAE_SERVICE")
    76  	version := r.attrs.EnvVar("GAE_VERSION")
    77  	instance := r.attrs.EnvVar("GAE_INSTANCE")
    78  	return service != "" && version != "" && instance != ""
    79  }
    80  
    81  func detectAppEngineResource() *mrpb.MonitoredResource {
    82  	projectID := detectedResource.metadataProjectID()
    83  	if projectID == "" {
    84  		projectID = detectedResource.attrs.EnvVar("GOOGLE_CLOUD_PROJECT")
    85  	}
    86  	if projectID == "" {
    87  		return nil
    88  	}
    89  	zone := detectedResource.metadataZone()
    90  	service := detectedResource.attrs.EnvVar("GAE_SERVICE")
    91  	version := detectedResource.attrs.EnvVar("GAE_VERSION")
    92  
    93  	return &mrpb.MonitoredResource{
    94  		Type: "gae_app",
    95  		Labels: map[string]string{
    96  			"project_id": projectID,
    97  			"module_id":  service,
    98  			"version_id": version,
    99  			"zone":       zone,
   100  		},
   101  	}
   102  }
   103  
   104  func (r *resource) isCloudFunction() bool {
   105  	target := r.attrs.EnvVar("FUNCTION_TARGET")
   106  	signature := r.attrs.EnvVar("FUNCTION_SIGNATURE_TYPE")
   107  	// note that this envvar is also present in Cloud Run environments
   108  	service := r.attrs.EnvVar("K_SERVICE")
   109  	return target != "" && signature != "" && service != ""
   110  }
   111  
   112  func detectCloudFunction() *mrpb.MonitoredResource {
   113  	projectID := detectedResource.metadataProjectID()
   114  	if projectID == "" {
   115  		return nil
   116  	}
   117  	region := detectedResource.metadataRegion()
   118  	functionName := detectedResource.attrs.EnvVar("K_SERVICE")
   119  	return &mrpb.MonitoredResource{
   120  		Type: "cloud_function",
   121  		Labels: map[string]string{
   122  			"project_id":    projectID,
   123  			"region":        region,
   124  			"function_name": functionName,
   125  		},
   126  	}
   127  }
   128  
   129  func (r *resource) isCloudRunService() bool {
   130  	config := r.attrs.EnvVar("K_CONFIGURATION")
   131  	// note that this envvar is also present in Cloud Function environments
   132  	service := r.attrs.EnvVar("K_SERVICE")
   133  	revision := r.attrs.EnvVar("K_REVISION")
   134  	return config != "" && service != "" && revision != ""
   135  }
   136  
   137  func (r *resource) isCloudRunJob() bool {
   138  	if r.attrs.EnvVar("CLOUD_RUN_JOB") == "" {
   139  		return false
   140  	}
   141  	if r.attrs.EnvVar("CLOUD_RUN_EXECUTION") == "" {
   142  		return false
   143  	}
   144  	if r.attrs.EnvVar("CLOUD_RUN_TASK_INDEX") == "" {
   145  		return false
   146  	}
   147  	if r.attrs.EnvVar("CLOUD_RUN_TASK_ATTEMPT") == "" {
   148  		return false
   149  	}
   150  	return true
   151  }
   152  
   153  func detectCloudRunServiceResource() *mrpb.MonitoredResource {
   154  	projectID := detectedResource.metadataProjectID()
   155  	if projectID == "" {
   156  		return nil
   157  	}
   158  	region := detectedResource.metadataRegion()
   159  	config := detectedResource.attrs.EnvVar("K_CONFIGURATION")
   160  	service := detectedResource.attrs.EnvVar("K_SERVICE")
   161  	revision := detectedResource.attrs.EnvVar("K_REVISION")
   162  	return &mrpb.MonitoredResource{
   163  		Type: "cloud_run_revision",
   164  		Labels: map[string]string{
   165  			"project_id":         projectID,
   166  			"location":           region,
   167  			"service_name":       service,
   168  			"revision_name":      revision,
   169  			"configuration_name": config,
   170  		},
   171  	}
   172  }
   173  
   174  func detectCloudRunJobResource() *mrpb.MonitoredResource {
   175  	projectID := detectedResource.metadataProjectID()
   176  	if projectID == "" {
   177  		return nil
   178  	}
   179  	region := detectedResource.metadataRegion()
   180  	job := detectedResource.attrs.EnvVar("CLOUD_RUN_JOB")
   181  	return &mrpb.MonitoredResource{
   182  		Type: "cloud_run_job",
   183  		Labels: map[string]string{
   184  			"project_id": projectID,
   185  			"location":   region,
   186  			"job_name":   job,
   187  		},
   188  	}
   189  }
   190  
   191  func (r *resource) isKubernetesEngine() bool {
   192  	clusterName := r.attrs.Metadata("instance/attributes/cluster-name")
   193  	if clusterName == "" {
   194  		return false
   195  	}
   196  	return true
   197  }
   198  
   199  func detectKubernetesResource() *mrpb.MonitoredResource {
   200  	projectID := detectedResource.metadataProjectID()
   201  	if projectID == "" {
   202  		return nil
   203  	}
   204  	clusterName := detectedResource.attrs.Metadata("instance/attributes/cluster-name")
   205  	clusterLocation := detectedResource.attrs.Metadata("instance/attributes/cluster-location")
   206  	namespaceName := detectedResource.attrs.ReadAll("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
   207  	if namespaceName == "" {
   208  		// if automountServiceAccountToken is disabled allow to customize
   209  		// the namespace via environment
   210  		namespaceName = detectedResource.attrs.EnvVar("NAMESPACE_NAME")
   211  	}
   212  	// note: if deployment customizes hostname, HOSTNAME envvar will have invalid content
   213  	podName := detectedResource.attrs.EnvVar("HOSTNAME")
   214  	// there is no way to derive container name from within container; use custom envvar if available
   215  	containerName := detectedResource.attrs.EnvVar("CONTAINER_NAME")
   216  	return &mrpb.MonitoredResource{
   217  		Type: "k8s_container",
   218  		Labels: map[string]string{
   219  			"cluster_name":   clusterName,
   220  			"location":       clusterLocation,
   221  			"project_id":     projectID,
   222  			"pod_name":       podName,
   223  			"namespace_name": namespaceName,
   224  			"container_name": containerName,
   225  		},
   226  	}
   227  }
   228  
   229  func (r *resource) isComputeEngine() bool {
   230  	preempted := r.attrs.Metadata("instance/preempted")
   231  	platform := r.attrs.Metadata("instance/cpu-platform")
   232  	appBucket := r.attrs.Metadata("instance/attributes/gae_app_bucket")
   233  	return preempted != "" && platform != "" && appBucket == ""
   234  }
   235  
   236  func detectComputeEngineResource() *mrpb.MonitoredResource {
   237  	projectID := detectedResource.metadataProjectID()
   238  	if projectID == "" {
   239  		return nil
   240  	}
   241  	id := detectedResource.attrs.Metadata("instance/id")
   242  	zone := detectedResource.metadataZone()
   243  	return &mrpb.MonitoredResource{
   244  		Type: "gce_instance",
   245  		Labels: map[string]string{
   246  			"project_id":  projectID,
   247  			"instance_id": id,
   248  			"zone":        zone,
   249  		},
   250  	}
   251  }
   252  
   253  func detectResource() *mrpb.MonitoredResource {
   254  	detectedResource.once.Do(func() {
   255  		if detectedResource.isMetadataActive() {
   256  			name := systemProductName()
   257  			switch {
   258  			case name == "Google App Engine", detectedResource.isAppEngine():
   259  				detectedResource.pb = detectAppEngineResource()
   260  			case name == "Google Cloud Functions", detectedResource.isCloudFunction():
   261  				detectedResource.pb = detectCloudFunction()
   262  			// cannot use name validation for Cloud Run resources because
   263  			// both of them set product name to "Google Cloud Run"
   264  			case detectedResource.isCloudRunService():
   265  				detectedResource.pb = detectCloudRunServiceResource()
   266  			case detectedResource.isCloudRunJob():
   267  				detectedResource.pb = detectCloudRunJobResource()
   268  			// cannot use name validation for GKE and GCE because
   269  			// both of them set product name to "Google Compute Engine"
   270  			case detectedResource.isKubernetesEngine():
   271  				detectedResource.pb = detectKubernetesResource()
   272  			case detectedResource.isComputeEngine():
   273  				detectedResource.pb = detectComputeEngineResource()
   274  			}
   275  		}
   276  	})
   277  	return detectedResource.pb
   278  }
   279  
   280  // systemProductName reads resource type on the Linux-based environments such as
   281  // Cloud Functions, Cloud Run, GKE, GCE, GAE, etc.
   282  func systemProductName() string {
   283  	if runtime.GOOS != "linux" {
   284  		// We don't have any non-Linux clues available, at least yet.
   285  		return ""
   286  	}
   287  	slurp := detectedResource.attrs.ReadAll("/sys/class/dmi/id/product_name")
   288  	return strings.TrimSpace(slurp)
   289  }
   290  
   291  var resourceInfo = map[string]struct{ rtype, label string }{
   292  	"organizations":   {"organization", "organization_id"},
   293  	"folders":         {"folder", "folder_id"},
   294  	"projects":        {"project", "project_id"},
   295  	"billingAccounts": {"billing_account", "account_id"},
   296  }
   297  
   298  func monitoredResource(parent string) *mrpb.MonitoredResource {
   299  	parts := strings.SplitN(parent, "/", 2)
   300  	if len(parts) != 2 {
   301  		return globalResource(parent)
   302  	}
   303  	info, ok := resourceInfo[parts[0]]
   304  	if !ok {
   305  		return globalResource(parts[1])
   306  	}
   307  	return &mrpb.MonitoredResource{
   308  		Type:   info.rtype,
   309  		Labels: map[string]string{info.label: parts[1]},
   310  	}
   311  }
   312  
   313  func globalResource(projectID string) *mrpb.MonitoredResource {
   314  	return &mrpb.MonitoredResource{
   315  		Type: "global",
   316  		Labels: map[string]string{
   317  			"project_id": projectID,
   318  		},
   319  	}
   320  }
   321  

View as plain text