899 lines
33 KiB
Python
899 lines
33 KiB
Python
import ConfigParser
|
|
import os
|
|
import itertools
|
|
import logging
|
|
import logging.config
|
|
import logging.handlers
|
|
import platform
|
|
import string
|
|
import subprocess
|
|
import sys
|
|
import glob
|
|
import inspect
|
|
import traceback
|
|
import re
|
|
import imp
|
|
from optparse import OptionParser, Values
|
|
from cStringIO import StringIO
|
|
import yaml
|
|
try:
|
|
from yaml import CLoader as Loader
|
|
except ImportError:
|
|
from yaml import Loader
|
|
|
|
# project
|
|
from util import get_os, Platform
|
|
from jmxfetch import JMXFetch, JMX_COLLECT_COMMAND
|
|
|
|
# CONSTANTS
|
|
AGENT_CONF = "agent.conf"
|
|
DEFAULT_CHECK_FREQUENCY = 15 # seconds
|
|
DEFAULT_STATSD_FREQUENCY = 2 # seconds
|
|
DEFAULT_STATSD_BUCKET_SIZE = 10 # seconds
|
|
LOGGING_MAX_BYTES = 5 * 1024 * 1024
|
|
|
|
log = logging.getLogger(__name__)
|
|
windows_file_handler_added = False
|
|
|
|
|
|
class PathNotFound(Exception):
|
|
pass
|
|
|
|
|
|
def get_parsed_args():
|
|
parser = OptionParser()
|
|
parser.add_option('-c', '--clean', action='store_true', default=False, dest='clean')
|
|
parser.add_option('-v', '--verbose', action='store_true', default=False, dest='verbose',
|
|
help='Print out stacktraces for errors in checks')
|
|
|
|
try:
|
|
options, args = parser.parse_args()
|
|
except SystemExit:
|
|
# Ignore parse errors
|
|
options, args = Values({'clean': False}), []
|
|
return options, args
|
|
|
|
|
|
def get_version():
|
|
return "4.3.0"
|
|
|
|
|
|
def skip_leading_wsp(f):
|
|
"Works on a file, returns a file-like object"
|
|
return StringIO("\n".join(map(string.strip, f.readlines())))
|
|
|
|
|
|
def _windows_commondata_path():
|
|
"""Return the common appdata path, using ctypes
|
|
From http://stackoverflow.com/questions/626796/\
|
|
how-do-i-find-the-windows-common-application-data-folder-using-python
|
|
"""
|
|
import ctypes
|
|
from ctypes import wintypes, windll
|
|
|
|
CSIDL_COMMON_APPDATA = 35
|
|
|
|
_SHGetFolderPath = windll.shell32.SHGetFolderPathW
|
|
_SHGetFolderPath.argtypes = [wintypes.HWND,
|
|
ctypes.c_int,
|
|
wintypes.HANDLE,
|
|
wintypes.DWORD, wintypes.LPCWSTR]
|
|
|
|
path_buf = wintypes.create_unicode_buffer(wintypes.MAX_PATH)
|
|
result = _SHGetFolderPath(0, CSIDL_COMMON_APPDATA, 0, 0, path_buf)
|
|
return path_buf.value
|
|
|
|
|
|
def _windows_config_path():
|
|
common_data = _windows_commondata_path()
|
|
path = os.path.join(common_data, 'Datadog', AGENT_CONF)
|
|
if os.path.exists(path):
|
|
return path
|
|
raise PathNotFound(path)
|
|
|
|
|
|
def _windows_confd_path():
|
|
common_data = _windows_commondata_path()
|
|
path = os.path.join(common_data, 'Datadog', 'conf.d')
|
|
if os.path.exists(path):
|
|
return path
|
|
raise PathNotFound(path)
|
|
|
|
|
|
def _windows_checksd_path():
|
|
if hasattr(sys, 'frozen'):
|
|
# we're frozen - from py2exe
|
|
prog_path = os.path.dirname(sys.executable)
|
|
checksd_path = os.path.join(prog_path, '..', 'checks.d')
|
|
else:
|
|
|
|
cur_path = os.path.dirname(__file__)
|
|
checksd_path = os.path.join(cur_path, 'checks.d')
|
|
|
|
if os.path.exists(checksd_path):
|
|
return checksd_path
|
|
raise PathNotFound(checksd_path)
|
|
|
|
|
|
def _unix_config_path():
|
|
path = os.path.join('/etc/mon-agent', AGENT_CONF)
|
|
if os.path.exists(path):
|
|
return path
|
|
elif os.path.exists('./%s' % AGENT_CONF):
|
|
return './%s' % AGENT_CONF
|
|
raise PathNotFound(path)
|
|
|
|
|
|
def _unix_confd_path():
|
|
path = os.path.join('/etc/mon-agent', 'conf.d')
|
|
if os.path.exists(path):
|
|
return path
|
|
raise PathNotFound(path)
|
|
|
|
|
|
def _unix_checksd_path():
|
|
# Unix only will look up based on the current directory
|
|
# because checks.d will hang with the other python modules
|
|
cur_path = os.path.dirname(os.path.realpath(__file__))
|
|
checksd_path = os.path.join(cur_path, 'checks.d')
|
|
|
|
if os.path.exists(checksd_path):
|
|
return checksd_path
|
|
raise PathNotFound(checksd_path)
|
|
|
|
|
|
def _is_affirmative(s):
|
|
return s.lower() in ('yes', 'true', '1')
|
|
|
|
|
|
def get_config_path(cfg_path=None, os_name=None):
|
|
# Check if there's an override and if it exists
|
|
if cfg_path is not None and os.path.exists(cfg_path):
|
|
return cfg_path
|
|
|
|
if os_name is None:
|
|
os_name = get_os()
|
|
|
|
# Check for an OS-specific path, continue on not-found exceptions
|
|
bad_path = ''
|
|
if os_name == 'windows':
|
|
try:
|
|
return _windows_config_path()
|
|
except PathNotFound, e:
|
|
if len(e.args) > 0:
|
|
bad_path = e.args[0]
|
|
else:
|
|
try:
|
|
return _unix_config_path()
|
|
except PathNotFound, e:
|
|
if len(e.args) > 0:
|
|
bad_path = e.args[0]
|
|
|
|
# Check if there's a config stored in the current agent directory
|
|
path = os.path.realpath(__file__)
|
|
path = os.path.dirname(path)
|
|
if os.path.exists(os.path.join(path, AGENT_CONF)):
|
|
return os.path.join(path, AGENT_CONF)
|
|
|
|
# If all searches fail, exit the agent with an error
|
|
sys.stderr.write("Please supply a configuration file at %s or in the directory where the Agent is currently deployed.\n" % bad_path)
|
|
sys.exit(3)
|
|
|
|
|
|
def get_config(parse_args=True, cfg_path=None, options=None):
|
|
if parse_args:
|
|
options, _ = get_parsed_args()
|
|
|
|
# General config
|
|
agentConfig = {
|
|
'check_freq': DEFAULT_CHECK_FREQUENCY,
|
|
'dogstatsd_interval': DEFAULT_STATSD_FREQUENCY,
|
|
'dogstatsd_agregator_bucket_size': DEFAULT_STATSD_BUCKET_SIZE,
|
|
'dogstatsd_normalize': 'yes',
|
|
'dogstatsd_port': 8125,
|
|
'forwarder_url': 'http://localhost:17123',
|
|
'hostname': None,
|
|
'listen_port': None,
|
|
'tags': None,
|
|
'version': get_version(),
|
|
'watchdog': True,
|
|
'additional_checksd': '/etc/mon-agent/checks.d/',
|
|
}
|
|
|
|
dogstatsd_interval = DEFAULT_STATSD_FREQUENCY
|
|
dogstatsd_agregator_bucket_size = DEFAULT_STATSD_BUCKET_SIZE
|
|
|
|
# Config handling
|
|
try:
|
|
# Find the right config file
|
|
path = os.path.realpath(__file__)
|
|
path = os.path.dirname(path)
|
|
|
|
config_path = get_config_path(cfg_path, os_name=get_os())
|
|
config = ConfigParser.ConfigParser()
|
|
config.readfp(skip_leading_wsp(open(config_path)))
|
|
|
|
# bulk import
|
|
for option in config.options('Main'):
|
|
agentConfig[option] = config.get('Main', option)
|
|
|
|
#
|
|
# Core config
|
|
#
|
|
|
|
# FIXME unnecessarily complex
|
|
|
|
# Extra checks.d path
|
|
# the linux directory is set by default
|
|
if config.has_option('Main', 'additional_checksd'):
|
|
agentConfig['additional_checksd'] = config.get('Main', 'additional_checksd')
|
|
elif get_os() == 'windows':
|
|
# default windows location
|
|
common_path = _windows_commondata_path()
|
|
agentConfig['additional_checksd'] = os.path.join(common_path, 'Datadog', 'checks.d')
|
|
|
|
# Concerns only Windows
|
|
if config.has_option('Main', 'use_web_info_page'):
|
|
agentConfig['use_web_info_page'] = config.get('Main', 'use_web_info_page').lower() in ("yes", "true")
|
|
else:
|
|
agentConfig['use_web_info_page'] = True
|
|
|
|
# local traffic only? Default to no
|
|
agentConfig['non_local_traffic'] = False
|
|
if config.has_option('Main', 'non_local_traffic'):
|
|
agentConfig['non_local_traffic'] = config.get('Main', 'non_local_traffic').lower() in ("yes", "true")
|
|
|
|
if config.has_option('Main', 'check_freq'):
|
|
try:
|
|
agentConfig['check_freq'] = int(config.get('Main', 'check_freq'))
|
|
except Exception:
|
|
pass
|
|
|
|
# Disable Watchdog (optionally)
|
|
if config.has_option('Main', 'watchdog'):
|
|
if config.get('Main', 'watchdog').lower() in ('no', 'false'):
|
|
agentConfig['watchdog'] = False
|
|
|
|
# Dogstatsd config
|
|
dogstatsd_defaults = {
|
|
'dogstatsd_port': 8125,
|
|
'dogstatsd_interval': dogstatsd_interval,
|
|
'dogstatsd_agregator_bucket_size': dogstatsd_agregator_bucket_size,
|
|
'dogstatsd_normalize': 'yes',
|
|
}
|
|
for key, value in dogstatsd_defaults.iteritems():
|
|
if config.has_option('Main', key):
|
|
agentConfig[key] = config.get('Main', key)
|
|
else:
|
|
agentConfig[key] = value
|
|
|
|
#Forwarding to external statsd server
|
|
if config.has_option('Main', 'statsd_forward_host'):
|
|
agentConfig['statsd_forward_host'] = config.get('Main', 'statsd_forward_host')
|
|
if config.has_option('Main', 'statsd_forward_port'):
|
|
agentConfig['statsd_forward_port'] = int(config.get('Main', 'statsd_forward_port'))
|
|
|
|
# normalize 'yes'/'no' to boolean
|
|
dogstatsd_defaults['dogstatsd_normalize'] = _is_affirmative(dogstatsd_defaults['dogstatsd_normalize'])
|
|
|
|
# Optional config
|
|
# FIXME not the prettiest code ever...
|
|
if config.has_option('Main', 'use_mount'):
|
|
agentConfig['use_mount'] = _is_affirmative(config.get('Main', 'use_mount'))
|
|
|
|
if config.has_option('Main', 'autorestart'):
|
|
agentConfig['autorestart'] = _is_affirmative(config.get('Main', 'autorestart'))
|
|
|
|
try:
|
|
filter_device_re = config.get('Main', 'device_blacklist_re')
|
|
agentConfig['device_blacklist_re'] = re.compile(filter_device_re)
|
|
except ConfigParser.NoOptionError:
|
|
pass
|
|
|
|
if config.has_option('datadog', 'ddforwarder_log'):
|
|
agentConfig['has_datadog'] = True
|
|
|
|
# Dogstream config
|
|
if config.has_option("Main", "dogstream_log"):
|
|
# Older version, single log support
|
|
log_path = config.get("Main", "dogstream_log")
|
|
if config.has_option("Main", "dogstream_line_parser"):
|
|
agentConfig["dogstreams"] = ':'.join([log_path, config.get("Main", "dogstream_line_parser")])
|
|
else:
|
|
agentConfig["dogstreams"] = log_path
|
|
|
|
elif config.has_option("Main", "dogstreams"):
|
|
agentConfig["dogstreams"] = config.get("Main", "dogstreams")
|
|
|
|
if config.has_option("Main", "nagios_perf_cfg"):
|
|
agentConfig["nagios_perf_cfg"] = config.get("Main", "nagios_perf_cfg")
|
|
|
|
if config.has_section('WMI'):
|
|
agentConfig['WMI'] = {}
|
|
for key, value in config.items('WMI'):
|
|
agentConfig['WMI'][key] = value
|
|
|
|
if config.has_option("Main", "limit_memory_consumption") and \
|
|
config.get("Main", "limit_memory_consumption") is not None:
|
|
agentConfig["limit_memory_consumption"] = int(config.get("Main", "limit_memory_consumption"))
|
|
else:
|
|
agentConfig["limit_memory_consumption"] = None
|
|
|
|
if config.has_option("Main", "skip_ssl_validation"):
|
|
agentConfig["skip_ssl_validation"] = _is_affirmative(config.get("Main", "skip_ssl_validation"))
|
|
|
|
agentConfig['Api'] = get_mon_api_config(config)
|
|
|
|
except ConfigParser.NoSectionError, e:
|
|
sys.stderr.write('Config file not found or incorrectly formatted.\n')
|
|
sys.exit(2)
|
|
|
|
except ConfigParser.ParsingError, e:
|
|
sys.stderr.write('Config file not found or incorrectly formatted.\n')
|
|
sys.exit(2)
|
|
|
|
except ConfigParser.NoOptionError, e:
|
|
sys.stderr.write('There are some items missing from your config file, but nothing fatal [%s]' % e)
|
|
|
|
# Storing proxy settings in the agentConfig
|
|
agentConfig['proxy_settings'] = get_proxy(agentConfig)
|
|
|
|
return agentConfig
|
|
|
|
|
|
def get_system_stats():
|
|
systemStats = {
|
|
'machine': platform.machine(),
|
|
'platform': sys.platform,
|
|
'processor': platform.processor(),
|
|
'pythonV': platform.python_version(),
|
|
}
|
|
|
|
platf = sys.platform
|
|
|
|
if Platform.is_linux(platf):
|
|
grep = subprocess.Popen(['grep', 'model name', '/proc/cpuinfo'], stdout=subprocess.PIPE, close_fds=True)
|
|
wc = subprocess.Popen(['wc', '-l'], stdin=grep.stdout, stdout=subprocess.PIPE, close_fds=True)
|
|
systemStats['cpuCores'] = int(wc.communicate()[0])
|
|
|
|
if Platform.is_darwin(platf):
|
|
systemStats['cpuCores'] = int(subprocess.Popen(['sysctl', 'hw.ncpu'], stdout=subprocess.PIPE, close_fds=True).communicate()[0].split(': ')[1])
|
|
|
|
if Platform.is_freebsd(platf):
|
|
systemStats['cpuCores'] = int(subprocess.Popen(['sysctl', 'hw.ncpu'], stdout=subprocess.PIPE, close_fds=True).communicate()[0].split(': ')[1])
|
|
|
|
if Platform.is_linux(platf):
|
|
systemStats['nixV'] = platform.dist()
|
|
|
|
elif Platform.is_darwin(platf):
|
|
systemStats['macV'] = platform.mac_ver()
|
|
|
|
elif Platform.is_freebsd(platf):
|
|
version = platform.uname()[2]
|
|
systemStats['fbsdV'] = ('freebsd', version, '') # no codename for FreeBSD
|
|
|
|
elif Platform.is_win32(platf):
|
|
systemStats['winV'] = platform.win32_ver()
|
|
|
|
return systemStats
|
|
|
|
|
|
def set_win32_cert_path():
|
|
"""In order to use tornado.httpclient with the packaged .exe on Windows we
|
|
need to override the default ceritifcate location which is based on the path
|
|
to tornado and will give something like "C:\path\to\program.exe\tornado/cert-file".
|
|
|
|
If pull request #379 is accepted (https://github.com/facebook/tornado/pull/379) we
|
|
will be able to override this in a clean way. For now, we have to monkey patch
|
|
tornado.httpclient._DEFAULT_CA_CERTS
|
|
"""
|
|
if hasattr(sys, 'frozen'):
|
|
# we're frozen - from py2exe
|
|
prog_path = os.path.dirname(sys.executable)
|
|
crt_path = os.path.join(prog_path, 'ca-certificates.crt')
|
|
else:
|
|
cur_path = os.path.dirname(__file__)
|
|
crt_path = os.path.join(cur_path, 'packaging', 'mon-agent', 'win32',
|
|
'install_files', 'ca-certificates.crt')
|
|
import tornado.simple_httpclient
|
|
log.info("Windows certificate path: %s" % crt_path)
|
|
tornado.simple_httpclient._DEFAULT_CA_CERTS = crt_path
|
|
|
|
def get_proxy(agentConfig, use_system_settings=False):
|
|
proxy_settings = {}
|
|
|
|
# First we read the proxy configuration from datadog.conf
|
|
proxy_host = agentConfig.get('proxy_host', None)
|
|
if proxy_host is not None and not use_system_settings:
|
|
proxy_settings['host'] = proxy_host
|
|
try:
|
|
proxy_settings['port'] = int(agentConfig.get('proxy_port', 3128))
|
|
except ValueError:
|
|
log.error('Proxy port must be an Integer. Defaulting it to 3128')
|
|
proxy_settings['port'] = 3128
|
|
|
|
proxy_settings['user'] = agentConfig.get('proxy_user', None)
|
|
proxy_settings['password'] = agentConfig.get('proxy_password', None)
|
|
proxy_settings['system_settings'] = False
|
|
log.debug("Proxy Settings: %s:%s@%s:%s" % (proxy_settings['user'], "*****", proxy_settings['host'], proxy_settings['port']))
|
|
return proxy_settings
|
|
|
|
# If no proxy configuration was specified in datadog.conf
|
|
# We try to read it from the system settings
|
|
try:
|
|
import urllib
|
|
proxies = urllib.getproxies()
|
|
proxy = proxies.get('https', None)
|
|
if proxy is not None:
|
|
try:
|
|
proxy = proxy.split('://')[1]
|
|
except Exception:
|
|
pass
|
|
px = proxy.split(':')
|
|
proxy_settings['host'] = px[0]
|
|
proxy_settings['port'] = px[1]
|
|
proxy_settings['user'] = None
|
|
proxy_settings['password'] = None
|
|
proxy_settings['system_settings'] = True
|
|
if '@' in proxy_settings['host']:
|
|
creds = proxy_settings['host'].split('@')[0].split(':')
|
|
proxy_settings['user'] = creds[0]
|
|
if len(creds) == 2:
|
|
proxy_settings['password'] = creds[1]
|
|
|
|
log.debug("Proxy Settings: %s:%s@%s:%s" % (proxy_settings['user'], "*****", proxy_settings['host'], proxy_settings['port']))
|
|
return proxy_settings
|
|
|
|
except Exception, e:
|
|
log.debug("Error while trying to fetch proxy settings using urllib %s. Proxy is probably not set" % str(e))
|
|
|
|
log.debug("No proxy configured")
|
|
|
|
return None
|
|
|
|
|
|
def get_confd_path(osname):
|
|
bad_path = ''
|
|
if osname == 'windows':
|
|
try:
|
|
return _windows_confd_path()
|
|
except PathNotFound, e:
|
|
if len(e.args) > 0:
|
|
bad_path = e.args[0]
|
|
else:
|
|
try:
|
|
return _unix_confd_path()
|
|
except PathNotFound, e:
|
|
if len(e.args) > 0:
|
|
bad_path = e.args[0]
|
|
|
|
cur_path = os.path.dirname(os.path.realpath(__file__))
|
|
cur_path = os.path.join(cur_path, 'conf.d')
|
|
|
|
if os.path.exists(cur_path):
|
|
return cur_path
|
|
|
|
raise PathNotFound(bad_path)
|
|
|
|
|
|
def get_checksd_path(osname):
|
|
if osname == 'windows':
|
|
return _windows_checksd_path()
|
|
else:
|
|
return _unix_checksd_path()
|
|
|
|
|
|
def get_win32service_file(osname, filename):
|
|
# This file is needed to log in the event viewer for windows
|
|
if osname == 'windows':
|
|
if hasattr(sys, 'frozen'):
|
|
# we're frozen - from py2exe
|
|
prog_path = os.path.dirname(sys.executable)
|
|
path = os.path.join(prog_path, filename)
|
|
else:
|
|
cur_path = os.path.dirname(__file__)
|
|
path = os.path.join(cur_path, filename)
|
|
if os.path.exists(path):
|
|
log.debug("Certificate file found at %s" % str(path))
|
|
return path
|
|
|
|
else:
|
|
cur_path = os.path.dirname(os.path.realpath(__file__))
|
|
path = os.path.join(cur_path, filename)
|
|
if os.path.exists(path):
|
|
return path
|
|
|
|
return None
|
|
|
|
|
|
def check_yaml(conf_path):
|
|
f = open(conf_path)
|
|
check_name = os.path.basename(conf_path).split('.')[0]
|
|
try:
|
|
check_config = yaml.load(f.read(), Loader=Loader)
|
|
assert 'init_config' in check_config, "No 'init_config' section found"
|
|
assert 'instances' in check_config, "No 'instances' section found"
|
|
|
|
valid_instances = True
|
|
if check_config['instances'] is None or not isinstance(check_config['instances'], list):
|
|
valid_instances = False
|
|
else:
|
|
for i in check_config['instances']:
|
|
if not isinstance(i, dict):
|
|
valid_instances = False
|
|
break
|
|
if not valid_instances:
|
|
raise Exception('You need to have at least one instance defined in the YAML file for this check')
|
|
else:
|
|
return check_config
|
|
finally:
|
|
f.close()
|
|
|
|
def load_check_directory(agentConfig):
|
|
''' Return the initialized checks from checks.d, and a mapping of checks that failed to
|
|
initialize. Only checks that have a configuration
|
|
file in conf.d will be returned. '''
|
|
from checks import AgentCheck
|
|
|
|
initialized_checks = {}
|
|
init_failed_checks = {}
|
|
|
|
osname = get_os()
|
|
checks_paths = [glob.glob(os.path.join(agentConfig['additional_checksd'], '*.py'))]
|
|
|
|
try:
|
|
checksd_path = get_checksd_path(osname)
|
|
checks_paths.append(glob.glob(os.path.join(checksd_path, '*.py')))
|
|
except PathNotFound, e:
|
|
log.error(e.args[0])
|
|
sys.exit(3)
|
|
|
|
try:
|
|
confd_path = get_confd_path(osname)
|
|
except PathNotFound, e:
|
|
log.error("No conf.d folder found at '%s' or in the directory where the Agent is currently deployed.\n" % e.args[0])
|
|
sys.exit(3)
|
|
|
|
# Start JMXFetch if needed
|
|
JMXFetch.init(confd_path, agentConfig, get_logging_config(), DEFAULT_CHECK_FREQUENCY, JMX_COLLECT_COMMAND)
|
|
|
|
# For backwards-compatability with old style checks, we have to load every
|
|
# checks.d module and check for a corresponding config OR check if the old
|
|
# config will "activate" the check.
|
|
#
|
|
# Once old-style checks aren't supported, we'll just read the configs and
|
|
# import the corresponding check module
|
|
for check in itertools.chain(*checks_paths):
|
|
check_name = os.path.basename(check).split('.')[0]
|
|
if check_name in initialized_checks or check_name in init_failed_checks:
|
|
log.debug('Skipping check %s because it has already been loaded from another location', check)
|
|
continue
|
|
try:
|
|
check_module = imp.load_source('checksd_%s' % check_name, check)
|
|
except Exception, e:
|
|
traceback_message = traceback.format_exc()
|
|
|
|
# Let's see if there is a conf.d for this check
|
|
conf_path = os.path.join(confd_path, '%s.yaml' % check_name)
|
|
if os.path.exists(conf_path):
|
|
# There is a configuration file for that check but the module can't be imported
|
|
init_failed_checks[check_name] = {'error':e, 'traceback':traceback_message}
|
|
log.exception('Unable to import check module %s.py from checks.d' % check_name)
|
|
else: # There is no conf for that check. Let's not spam the logs for it.
|
|
log.debug('Unable to import check module %s.py from checks.d' % check_name)
|
|
continue
|
|
|
|
check_class = None
|
|
classes = inspect.getmembers(check_module, inspect.isclass)
|
|
for _, clsmember in classes:
|
|
if clsmember == AgentCheck:
|
|
continue
|
|
if issubclass(clsmember, AgentCheck):
|
|
check_class = clsmember
|
|
if AgentCheck in clsmember.__bases__:
|
|
continue
|
|
else:
|
|
break
|
|
|
|
if not check_class:
|
|
log.error('No check class (inheriting from AgentCheck) found in %s.py' % check_name)
|
|
continue
|
|
|
|
# Check if the config exists OR we match the old-style config
|
|
conf_path = os.path.join(confd_path, '%s.yaml' % check_name)
|
|
if os.path.exists(conf_path):
|
|
f = open(conf_path)
|
|
try:
|
|
check_config = check_yaml(conf_path)
|
|
except Exception, e:
|
|
log.exception("Unable to parse yaml config in %s" % conf_path)
|
|
traceback_message = traceback.format_exc()
|
|
init_failed_checks[check_name] = {'error':e, 'traceback':traceback_message}
|
|
continue
|
|
elif hasattr(check_class, 'parse_agent_config'):
|
|
# FIXME: Remove this check once all old-style checks are gone
|
|
try:
|
|
check_config = check_class.parse_agent_config(agentConfig)
|
|
except Exception, e:
|
|
continue
|
|
if not check_config:
|
|
continue
|
|
d = [
|
|
"Configuring %s in datadog.conf is deprecated." % (check_name),
|
|
"Please use conf.d. In a future release, support for the",
|
|
"old style of configuration will be dropped.",
|
|
]
|
|
log.warn(" ".join(d))
|
|
|
|
else:
|
|
log.debug('No conf.d/%s.yaml found for checks.d/%s.py' % (check_name, check_name))
|
|
continue
|
|
|
|
# Look for the per-check config, which *must* exist
|
|
if not check_config.get('instances'):
|
|
log.error("Config %s is missing 'instances'" % conf_path)
|
|
continue
|
|
|
|
# Init all of the check's classes with
|
|
init_config = check_config.get('init_config', {})
|
|
# init_config: in the configuration triggers init_config to be defined
|
|
# to None.
|
|
if init_config is None:
|
|
init_config = {}
|
|
|
|
instances = check_config['instances']
|
|
try:
|
|
try:
|
|
c = check_class(check_name, init_config=init_config,
|
|
agentConfig=agentConfig, instances=instances)
|
|
except TypeError, e:
|
|
# Backwards compatibility for checks which don't support the
|
|
# instances argument in the constructor.
|
|
c = check_class(check_name, init_config=init_config,
|
|
agentConfig=agentConfig)
|
|
c.instances = instances
|
|
except Exception, e:
|
|
log.exception('Unable to initialize check %s' % check_name)
|
|
traceback_message = traceback.format_exc()
|
|
init_failed_checks[check_name] = {'error':e, 'traceback':traceback_message}
|
|
else:
|
|
initialized_checks[check_name] = c
|
|
|
|
# Add custom pythonpath(s) if available
|
|
if 'pythonpath' in check_config:
|
|
pythonpath = check_config['pythonpath']
|
|
if not isinstance(pythonpath, list):
|
|
pythonpath = [pythonpath]
|
|
sys.path.extend(pythonpath)
|
|
|
|
log.debug('Loaded check.d/%s.py' % check_name)
|
|
|
|
log.info('initialized checks.d checks: %s' % initialized_checks.keys())
|
|
log.info('initialization failed checks.d checks: %s' % init_failed_checks.keys())
|
|
return {'initialized_checks':initialized_checks.values(),
|
|
'init_failed_checks':init_failed_checks,
|
|
}
|
|
|
|
|
|
#
|
|
# logging
|
|
|
|
def get_log_date_format():
|
|
return "%Y-%m-%d %H:%M:%S %Z"
|
|
|
|
def get_log_format(logger_name):
|
|
if get_os() != 'windows':
|
|
return '%%(asctime)s | %%(levelname)s | dd.%s | %%(name)s(%%(filename)s:%%(lineno)s) | %%(message)s' % logger_name
|
|
return '%(asctime)s | %(levelname)s | %(name)s(%(filename)s:%(lineno)s) | %(message)s'
|
|
|
|
|
|
def get_syslog_format(logger_name):
|
|
return 'dd.%s[%%(process)d]: %%(levelname)s (%%(filename)s:%%(lineno)s): %%(message)s' % logger_name
|
|
|
|
|
|
def get_logging_config(cfg_path=None):
|
|
system_os = get_os()
|
|
if system_os != 'windows':
|
|
logging_config = {
|
|
'log_level': None,
|
|
'collector_log_file': '/var/log/datadog/collector.log',
|
|
'forwarder_log_file': '/var/log/datadog/forwarder.log',
|
|
'dogstatsd_log_file': '/var/log/datadog/dogstatsd.log',
|
|
'jmxfetch_log_file': '/var/log/datadog/jmxfetch.log',
|
|
'log_to_event_viewer': False,
|
|
'log_to_syslog': True,
|
|
'syslog_host': None,
|
|
'syslog_port': None,
|
|
}
|
|
else:
|
|
windows_log_location = os.path.join(_windows_commondata_path(), 'Datadog', 'logs', 'ddagent.log')
|
|
jmxfetch_log_file = os.path.join(_windows_commondata_path(), 'Datadog', 'logs', 'jmxfetch.log')
|
|
logging_config = {
|
|
'log_level': None,
|
|
'ddagent_log_file': windows_log_location,
|
|
'jmxfetch_log_file': jmxfetch_log_file,
|
|
'log_to_event_viewer': False,
|
|
'log_to_syslog': False,
|
|
'syslog_host': None,
|
|
'syslog_port': None,
|
|
}
|
|
|
|
config_path = get_config_path(cfg_path, os_name=system_os)
|
|
config = ConfigParser.ConfigParser()
|
|
config.readfp(skip_leading_wsp(open(config_path)))
|
|
|
|
if config.has_section('handlers') or config.has_section('loggers') or config.has_section('formatters'):
|
|
if system_os == 'windows':
|
|
config_example_file = "https://github.com/DataDog/dd-agent/blob/master/packaging/datadog-agent/win32/install_files/datadog_win32.conf"
|
|
else:
|
|
config_example_file = "https://github.com/DataDog/dd-agent/blob/master/datadog.conf.example"
|
|
|
|
sys.stderr.write("""Python logging config is no longer supported and will be ignored.
|
|
To configure logging, update the logging portion of 'datadog.conf' to match:
|
|
'%s'.
|
|
""" % config_example_file)
|
|
|
|
for option in logging_config:
|
|
if config.has_option('Main', option):
|
|
logging_config[option] = config.get('Main', option)
|
|
|
|
levels = {
|
|
'CRITICAL': logging.CRITICAL,
|
|
'DEBUG': logging.DEBUG,
|
|
'ERROR': logging.ERROR,
|
|
'FATAL': logging.FATAL,
|
|
'INFO': logging.INFO,
|
|
'WARN': logging.WARN,
|
|
'WARNING': logging.WARNING,
|
|
}
|
|
if config.has_option('Main', 'log_level'):
|
|
logging_config['log_level'] = levels.get(config.get('Main', 'log_level'))
|
|
|
|
if config.has_option('Main', 'log_to_syslog'):
|
|
logging_config['log_to_syslog'] = config.get('Main', 'log_to_syslog').strip().lower() in ['yes', 'true', 1]
|
|
|
|
if config.has_option('Main', 'log_to_event_viewer'):
|
|
logging_config['log_to_event_viewer'] = config.get('Main', 'log_to_event_viewer').strip().lower() in ['yes', 'true', 1]
|
|
|
|
if config.has_option('Main', 'syslog_host'):
|
|
host = config.get('Main', 'syslog_host').strip()
|
|
if host:
|
|
logging_config['syslog_host'] = host
|
|
else:
|
|
logging_config['syslog_host'] = None
|
|
|
|
if config.has_option('Main', 'syslog_port'):
|
|
port = config.get('Main', 'syslog_port').strip()
|
|
try:
|
|
logging_config['syslog_port'] = int(port)
|
|
except Exception:
|
|
logging_config['syslog_port'] = None
|
|
|
|
if config.has_option('Main', 'disable_file_logging'):
|
|
logging_config['disable_file_logging'] = config.get('Main', 'disable_file_logging').strip().lower() in ['yes', 'true', 1]
|
|
else:
|
|
logging_config['disable_file_logging'] = False
|
|
|
|
return logging_config
|
|
|
|
|
|
|
|
def initialize_logging(logger_name):
|
|
global windows_file_handler_added
|
|
try:
|
|
logging_config = get_logging_config()
|
|
|
|
logging.basicConfig(
|
|
format=get_log_format(logger_name),
|
|
level=logging_config['log_level'] or logging.INFO,
|
|
)
|
|
|
|
# set up file loggers
|
|
if get_os() == 'windows' and not windows_file_handler_added:
|
|
logger_name = 'ddagent'
|
|
windows_file_handler_added = True
|
|
|
|
log_file = logging_config.get('%s_log_file' % logger_name)
|
|
if log_file is not None and not logging_config['disable_file_logging']:
|
|
# make sure the log directory is writeable
|
|
# NOTE: the entire directory needs to be writable so that rotation works
|
|
if os.access(os.path.dirname(log_file), os.R_OK | os.W_OK):
|
|
file_handler = logging.handlers.RotatingFileHandler(log_file, maxBytes=LOGGING_MAX_BYTES, backupCount=1)
|
|
formatter = logging.Formatter(get_log_format(logger_name), get_log_date_format())
|
|
file_handler.setFormatter(formatter)
|
|
|
|
root_log = logging.getLogger()
|
|
root_log.addHandler(file_handler)
|
|
else:
|
|
sys.stderr.write("Log file is unwritable: '%s'\n" % log_file)
|
|
|
|
# set up syslog
|
|
if logging_config['log_to_syslog']:
|
|
try:
|
|
from logging.handlers import SysLogHandler
|
|
|
|
if logging_config['syslog_host'] is not None and logging_config['syslog_port'] is not None:
|
|
sys_log_addr = (logging_config['syslog_host'], logging_config['syslog_port'])
|
|
else:
|
|
sys_log_addr = "/dev/log"
|
|
# Special-case macs
|
|
if sys.platform == 'darwin':
|
|
sys_log_addr = "/var/run/syslog"
|
|
|
|
handler = SysLogHandler(address=sys_log_addr, facility=SysLogHandler.LOG_DAEMON)
|
|
handler.setFormatter(logging.Formatter(get_syslog_format(logger_name), get_log_date_format()))
|
|
root_log = logging.getLogger()
|
|
root_log.addHandler(handler)
|
|
except Exception, e:
|
|
sys.stderr.write("Error setting up syslog: '%s'\n" % str(e))
|
|
traceback.print_exc()
|
|
|
|
# Setting up logging in the event viewer for windows
|
|
if get_os() == 'windows' and logging_config['log_to_event_viewer']:
|
|
try:
|
|
from logging.handlers import NTEventLogHandler
|
|
nt_event_handler = NTEventLogHandler(logger_name,get_win32service_file('windows', 'win32service.pyd'), 'Application')
|
|
nt_event_handler.setFormatter(logging.Formatter(get_syslog_format(logger_name), get_log_date_format()))
|
|
nt_event_handler.setLevel(logging.ERROR)
|
|
app_log = logging.getLogger(logger_name)
|
|
app_log.addHandler(nt_event_handler)
|
|
except Exception, e:
|
|
sys.stderr.write("Error setting up Event viewer logging: '%s'\n" % str(e))
|
|
traceback.print_exc()
|
|
|
|
except Exception, e:
|
|
sys.stderr.write("Couldn't initialize logging: %s\n" % str(e))
|
|
traceback.print_exc()
|
|
|
|
# if config fails entirely, enable basic stdout logging as a fallback
|
|
logging.basicConfig(
|
|
format=get_log_format(logger_name),
|
|
level=logging.INFO,
|
|
)
|
|
|
|
# re-get the log after logging is initialized
|
|
global log
|
|
log = logging.getLogger(__name__)
|
|
|
|
def get_mon_api_config(config):
|
|
mon_api_config = {'is_enabled': False,
|
|
'url': '',
|
|
'project_id': '',
|
|
'username': '',
|
|
'password': False,
|
|
'use_keystone': False,
|
|
'keystone_url': '',
|
|
'aggregate_metrics': True,
|
|
'mapping_file': ''}
|
|
|
|
if config.has_section("Api"):
|
|
|
|
if config.has_option("Api", "use_mon_api"):
|
|
mon_api_config["use_mon_api"] = config.getboolean("Api", "use_mon_api")
|
|
|
|
if config.has_option("Api", "url"):
|
|
mon_api_config["url"] = config.get("Api", "url")
|
|
|
|
if config.has_option("Api", "project_id"):
|
|
mon_api_config["project_id"] = config.get("Api", "project_id")
|
|
|
|
if config.has_option("Api", "username"):
|
|
mon_api_config["username"] = config.get("Api", "username")
|
|
|
|
if config.has_option("Api", "password"):
|
|
mon_api_config["password"] = config.get("Api", "password")
|
|
|
|
if config.has_option("Api", "use_keystone"):
|
|
mon_api_config["use_keystone"] = config.getboolean("Api", "use_keystone")
|
|
|
|
if config.has_option("Api", "keystone_url"):
|
|
mon_api_config["keystone_url"] = config.get("Api", "keystone_url")
|
|
|
|
if config.has_option("Api", "aggregate_metrics"):
|
|
mon_api_config["aggregate_metrics"] = config.getboolean("Api", "aggregate_metrics")
|
|
|
|
if config.has_option("Api", "mapping_file"):
|
|
mon_api_config["mapping_file"] = config.get("Api", "mapping_file")
|
|
|
|
return mon_api_config
|