resource.go 8.6 KB

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