1import datetime 2import logging 3import os 4import platform 5import re 6import sys 7from typing import Any 8from typing import Dict 9from typing import TYPE_CHECKING 10from typing import Union 11 12import ddtrace 13from ddtrace.internal.writer import AgentWriter 14from ddtrace.internal.writer import LogWriter 15from ddtrace.sampler import DatadogSampler 16 17from .logger import get_logger 18 19 20if TYPE_CHECKING: 21 from ddtrace import Tracer 22 23 24logger = get_logger(__name__) 25 26 27def in_venv(): 28 # type: () -> bool 29 # Works with both venv and virtualenv 30 # https://stackoverflow.com/a/42580137 31 return ( 32 "VIRTUAL_ENV" in os.environ 33 or hasattr(sys, "real_prefix") 34 or (hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix) 35 ) 36 37 38def tags_to_str(tags): 39 # type: (Dict[str, Any]) -> str 40 # Turn a dict of tags to a string "k1:v1,k2:v2,..." 41 return ",".join(["%s:%s" % (k, v) for k, v in tags.items()]) 42 43 44def collect(tracer): 45 # type: (Tracer) -> Dict[str, Any] 46 """Collect system and library information into a serializable dict.""" 47 48 import pkg_resources 49 50 from ddtrace.internal.runtime.runtime_metrics import RuntimeWorker 51 52 if isinstance(tracer.writer, LogWriter): 53 agent_url = "AGENTLESS" 54 agent_error = None 55 elif isinstance(tracer.writer, AgentWriter): 56 writer = tracer.writer 57 agent_url = writer.agent_url 58 try: 59 writer.write([]) 60 writer.flush_queue(raise_exc=True) 61 except Exception as e: 62 agent_error = "Agent not reachable at %s. Exception raised: %s" % (agent_url, str(e)) 63 else: 64 agent_error = None 65 else: 66 agent_url = "CUSTOM" 67 agent_error = None 68 69 sampler_rules = None 70 if isinstance(tracer.sampler, DatadogSampler): 71 sampler_rules = [str(rule) for rule in tracer.sampler.rules] 72 73 is_venv = in_venv() 74 75 packages_available = {p.project_name: p.version for p in pkg_resources.working_set} 76 integration_configs = {} # type: Dict[str, Union[Dict[str, Any], str]] 77 for module, enabled in ddtrace._monkey.PATCH_MODULES.items(): 78 # TODO: this check doesn't work in all cases... we need a mapping 79 # between the module and the library name. 80 module_available = module in packages_available 81 module_instrumented = module in ddtrace._monkey._PATCHED_MODULES 82 module_imported = module in sys.modules 83 84 if enabled: 85 # Note that integration configs aren't added until the integration 86 # module is imported. This typically occurs as a side-effect of 87 # patch(). 88 # This also doesn't load work in all cases since we don't always 89 # name the configuration entry the same as the integration module 90 # name :/ 91 config = ddtrace.config._config.get(module, "N/A") 92 else: 93 config = None 94 95 if module_available: 96 integration_configs[module] = dict( 97 enabled=enabled, 98 instrumented=module_instrumented, 99 module_available=module_available, 100 module_version=packages_available[module], 101 module_imported=module_imported, 102 config=config, 103 ) 104 else: 105 # Use N/A here to avoid the additional clutter of an entire 106 # config dictionary for a module that isn't available. 107 integration_configs[module] = "N/A" 108 109 pip_version = packages_available.get("pip", "N/A") 110 111 return dict( 112 # Timestamp UTC ISO 8601 113 date=datetime.datetime.utcnow().isoformat(), 114 # eg. "Linux", "Darwin" 115 os_name=platform.system(), 116 # eg. 12.5.0 117 os_version=platform.release(), 118 is_64_bit=sys.maxsize > 2 ** 32, 119 architecture=platform.architecture()[0], 120 vm=platform.python_implementation(), 121 version=ddtrace.__version__, 122 lang="python", 123 lang_version=platform.python_version(), 124 pip_version=pip_version, 125 in_virtual_env=is_venv, 126 agent_url=agent_url, 127 agent_error=agent_error, 128 env=ddtrace.config.env or "", 129 is_global_tracer=tracer == ddtrace.tracer, 130 enabled_env_setting=os.getenv("DATADOG_TRACE_ENABLED"), 131 tracer_enabled=tracer.enabled, 132 sampler_type=type(tracer.sampler).__name__ if tracer.sampler else "N/A", 133 priority_sampler_type=type(tracer.priority_sampler).__name__ if tracer.priority_sampler else "N/A", 134 sampler_rules=sampler_rules, 135 service=ddtrace.config.service or "", 136 debug=ddtrace.tracer.log.isEnabledFor(logging.DEBUG), 137 enabled_cli="ddtrace" in os.getenv("PYTHONPATH", ""), 138 analytics_enabled=ddtrace.config.analytics_enabled, 139 log_injection_enabled=ddtrace.config.logs_injection, 140 health_metrics_enabled=ddtrace.config.health_metrics_enabled, 141 runtime_metrics_enabled=RuntimeWorker.enabled, 142 dd_version=ddtrace.config.version or "", 143 priority_sampling_enabled=tracer.priority_sampler is not None, 144 global_tags=os.getenv("DD_TAGS", ""), 145 tracer_tags=tags_to_str(tracer.tags), 146 integrations=integration_configs, 147 partial_flush_enabled=tracer._partial_flush_enabled, 148 partial_flush_min_spans=tracer._partial_flush_min_spans, 149 ) 150 151 152def pretty_collect(tracer, color=True): 153 class bcolors: 154 HEADER = "\033[95m" 155 OKBLUE = "\033[94m" 156 OKCYAN = "\033[96m" 157 OKGREEN = "\033[92m" 158 WARNING = "\033[93m" 159 FAIL = "\033[91m" 160 ENDC = "\033[0m" 161 BOLD = "\033[1m" 162 163 info = collect(tracer) 164 165 info_pretty = """{blue}{bold}Tracer Configurations:{end} 166 Tracer enabled: {tracer_enabled} 167 Debug logging: {debug} 168 Writing traces to: {agent_url} 169 Agent error: {agent_error} 170 App Analytics enabled(deprecated): {analytics_enabled} 171 Log injection enabled: {log_injection_enabled} 172 Health metrics enabled: {health_metrics_enabled} 173 Priority sampling enabled: {priority_sampling_enabled} 174 Partial flushing enabled: {partial_flush_enabled} 175 Partial flush minimum number of spans: {partial_flush_min_spans} 176 {green}{bold}Tagging:{end} 177 DD Service: {service} 178 DD Env: {env} 179 DD Version: {dd_version} 180 Global Tags: {global_tags} 181 Tracer Tags: {tracer_tags}""".format( 182 tracer_enabled=info.get("tracer_enabled"), 183 debug=info.get("debug"), 184 agent_url=info.get("agent_url") or "Not writing at the moment, is your tracer running?", 185 agent_error=info.get("agent_error") or "None", 186 analytics_enabled=info.get("analytics_enabled"), 187 log_injection_enabled=info.get("log_injection_enabled"), 188 health_metrics_enabled=info.get("health_metrics_enabled"), 189 priority_sampling_enabled=info.get("priority_sampling_enabled"), 190 partial_flush_enabled=info.get("partial_flush_enabled"), 191 partial_flush_min_spans=info.get("partial_flush_min_spans") or "Not set", 192 service=info.get("service") or "None", 193 env=info.get("env") or "None", 194 dd_version=info.get("dd_version") or "None", 195 global_tags=info.get("global_tags") or "None", 196 tracer_tags=info.get("tracer_tags") or "None", 197 blue=bcolors.OKBLUE, 198 green=bcolors.OKGREEN, 199 bold=bcolors.BOLD, 200 end=bcolors.ENDC, 201 ) 202 203 summary = "{0}{1}Summary{2}".format(bcolors.OKCYAN, bcolors.BOLD, bcolors.ENDC) 204 205 if info.get("agent_error"): 206 summary += ( 207 "\n\n{fail}ERROR: It looks like you have an agent error: '{agent_error}'\n If you're experiencing" 208 " a connection error, please make sure you've followed the setup for your particular environment so that " 209 "the tracer and Datadog agent are configured properly to connect, and that the Datadog agent is running: " 210 "https://ddtrace.readthedocs.io/en/stable/troubleshooting.html#failed-to-send-traces-connectionrefused" 211 "error" 212 "\nIf your issue is not a connection error then please reach out to support for further assistance:" 213 " https://docs.datadoghq.com/help/{end}" 214 ).format(fail=bcolors.FAIL, agent_error=info.get("agent_error"), end=bcolors.ENDC) 215 216 if not info.get("service"): 217 summary += ( 218 "\n\n{warning}WARNING SERVICE NOT SET: It is recommended that a service tag be set for all traced" 219 " applications. For more information please see" 220 " https://ddtrace.readthedocs.io/en/stable/troubleshooting.html{end}" 221 ).format(warning=bcolors.WARNING, end=bcolors.ENDC) 222 223 if not info.get("env"): 224 summary += ( 225 "\n\n{warning}WARNING ENV NOT SET: It is recommended that an env tag be set for all traced" 226 " applications. For more information please see " 227 "https://ddtrace.readthedocs.io/en/stable/troubleshooting.html{end}" 228 ).format(warning=bcolors.WARNING, end=bcolors.ENDC) 229 230 if not info.get("dd_version"): 231 summary += ( 232 "\n\n{warning}WARNING VERSION NOT SET: It is recommended that a version tag be set for all traced" 233 " applications. For more information please see" 234 " https://ddtrace.readthedocs.io/en/stable/troubleshooting.html{end}" 235 ).format(warning=bcolors.WARNING, end=bcolors.ENDC) 236 237 info_pretty += "\n\n" + summary 238 239 if color is False: 240 return escape_ansi(info_pretty) 241 242 return info_pretty 243 244 245def escape_ansi(line): 246 ansi_escape = re.compile(r"(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]") 247 return ansi_escape.sub("", line) 248