blob: 2990a53d027071228cb1fe8cee122f037da6c547 [file] [log] [blame]
# Copyright (c) 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import logging
import subprocess
import tempfile
import threading
import infra_libs.logs.logs
class DummyCloudtailFactory(object):
def start(self, *_args, **_kwargs):
raise OSError('Cloudtail is not configured')
class CloudtailFactory(object):
"""Object that knows how to start cloudtail processes."""
def __init__(self, path, ts_mon_credentials):
self._path = path
self._ts_mon_credentials = ts_mon_credentials
# In practice, cloudtail is launched only once per daemonized process by
# the daemon process itself. So, at the moment, the state here isn't
# strictly speaking necessary. However, given "Factory" in class name, it's
# easy to imagine unsuspecting devs expecting to be able to do several
# factory uses per process lifetime.
self._lock = threading.Lock()
self._counter = 0
self._log_dir = None
def start(self, log_name, stdin_fh, **popen_kwargs):
"""Starts cloudtail in a subprocess. Thread-safe.
Args:
log_name: --log-id argument passed to cloudtail.
stdin_fh: File object or descriptor to be connected to cloudtail's stdin.
popen_kwargs: Any additional keyword arguments to pass to Popen.
Raises:
OSError
"""
args = [
self._path, 'pipe',
'--log-id', log_name,
'--local-log-level', 'info',
'--local-log-file', self._get_log_file(),
]
if self._ts_mon_credentials:
args.extend(['--ts-mon-credentials', self._ts_mon_credentials])
with open(os.devnull, 'w') as null_fh:
subprocess.Popen(
args,
stdin=stdin_fh,
stdout=null_fh,
stderr=null_fh,
**popen_kwargs)
def _get_log_file(self):
with self._lock:
if self._log_dir is None:
self._log_dir = _choose_log_dir()
self._counter += 1
counter = self._counter
return os.path.join(
self._log_dir, 'sm.%d.cloudtail.%d.log' % (os.getpid(), counter))
def _choose_log_dir():
# On Chrome puppet managed machines, one of the log directories should exist.
# NOTE: DEFAULT_LOG_DIRECTORIES is a string, not a list.
candidates = infra_libs.logs.logs.DEFAULT_LOG_DIRECTORIES.split(os.pathsep)
for d in candidates:
try:
with tempfile.TemporaryFile(dir=d):
pass
return d
except OSError:
continue
d = tempfile.gettempdir()
logging.warn(
'default infra log %s dirs not writable, using %s instead', candidates, d)
return d