blob: f8416e436c268547d19715b7cdebef7efde794ad [file] [log] [blame]
// 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) isCloudRun() 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 detectCloudRunResource() *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 (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
}
zone := detectedResource.metadataZone()
clusterName := detectedResource.attrs.Metadata("instance/attributes/cluster-name")
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": zone,
"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()
case name == "Google Cloud Run", detectedResource.isCloudRun():
detectedResource.pb = detectCloudRunResource()
// 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,
},
}
}