/ services / internalLogging.ts
internalLogging.ts
 1  import { readFile } from 'fs/promises'
 2  import memoize from 'lodash-es/memoize.js'
 3  import type { ToolPermissionContext } from '../Tool.js'
 4  import { jsonStringify } from '../utils/slowOperations.js'
 5  import {
 6    type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
 7    logEvent,
 8  } from './analytics/index.js'
 9  
10  /**
11   * Get the current Kubernetes namespace:
12   * Returns null on laptops/local development,
13   * "default" for devboxes in default namespace,
14   * "ts" for devboxes in ts namespace,
15   * ...
16   */
17  const getKubernetesNamespace = memoize(async (): Promise<string | null> => {
18    if (process.env.USER_TYPE !== 'ant') {
19      return null
20    }
21    const namespacePath =
22      '/var/run/secrets/kubernetes.io/serviceaccount/namespace'
23    const namespaceNotFound = 'namespace not found'
24    try {
25      const content = await readFile(namespacePath, { encoding: 'utf8' })
26      return content.trim()
27    } catch {
28      return namespaceNotFound
29    }
30  })
31  
32  /**
33   * Get the OCI container ID from within a running container
34   */
35  export const getContainerId = memoize(async (): Promise<string | null> => {
36    if (process.env.USER_TYPE !== 'ant') {
37      return null
38    }
39    const containerIdPath = '/proc/self/mountinfo'
40    const containerIdNotFound = 'container ID not found'
41    const containerIdNotFoundInMountinfo = 'container ID not found in mountinfo'
42    try {
43      const mountinfo = (
44        await readFile(containerIdPath, { encoding: 'utf8' })
45      ).trim()
46  
47      // Pattern to match both Docker and containerd/CRI-O container IDs
48      // Docker: /docker/containers/[64-char-hex]
49      // Containerd: /sandboxes/[64-char-hex]
50      const containerIdPattern =
51        /(?:\/docker\/containers\/|\/sandboxes\/)([0-9a-f]{64})/
52  
53      const lines = mountinfo.split('\n')
54  
55      for (const line of lines) {
56        const match = line.match(containerIdPattern)
57        if (match && match[1]) {
58          return match[1]
59        }
60      }
61  
62      return containerIdNotFoundInMountinfo
63    } catch {
64      return containerIdNotFound
65    }
66  })
67  
68  /**
69   * Logs an event with the current namespace and tool permission context
70   */
71  export async function logPermissionContextForAnts(
72    toolPermissionContext: ToolPermissionContext | null,
73    moment: 'summary' | 'initialization',
74  ): Promise<void> {
75    if (process.env.USER_TYPE !== 'ant') {
76      return
77    }
78  
79    void logEvent('tengu_internal_record_permission_context', {
80      moment:
81        moment as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
82      namespace:
83        (await getKubernetesNamespace()) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
84      toolPermissionContext: jsonStringify(
85        toolPermissionContext,
86      ) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
87      containerId:
88        (await getContainerId()) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
89    })
90  }