def call(self, module, method, wrapped, instance, args, kwargs): signature = "psycopg2.connect" host = kwargs.get("host") if host: signature += " " + compat.text_type(host) port = kwargs.get("port") if port: port = str(port) if int(port) != default_ports.get("postgresql"): host += ":" + port signature += " " + compat.text_type(host) else: # Parse connection string and extract host/port pass destination_info = { "address": kwargs.get("host", "localhost"), "port": int(kwargs.get("port", default_ports.get("postgresql"))), "service": { "name": "postgresql", "resource": "postgresql", "type": "db" }, } with capture_span( signature, span_type="db", span_subtype="postgresql", span_action="connect", extra={"destination": destination_info}, ): return PGConnectionProxy(wrapped(*args, **kwargs), destination_info=destination_info)
def update_config(self): if not self.transport: logger.warning("No transport set for config updates, skipping") return logger.debug("Checking for new config...") keys = {"service": {"name": self.service_name}} if self.environment: keys["service"]["environment"] = self.environment new_version, new_config, next_run = self.transport.get_config( self.config_version, keys) if new_version and new_config: errors = self.update(new_version, **new_config) if errors: logger.error("Error applying new configuration: %s", repr(errors)) else: logger.info( "Applied new configuration: %s", "; ".join("%s=%s" % (compat.text_type(k), compat.text_type(v)) for k, v in compat.iteritems(new_config)), ) elif new_version == self.config_version: logger.debug("Remote config unchanged") elif not new_config and self.changed: logger.debug("Remote config disappeared, resetting to original") self.reset() return next_run
def enforce_label_format(labels): """ Enforces label format: * dots, double quotes or stars in keys are replaced by underscores * string values are limited to a length of 1024 characters * values can only be of a limited set of types :param labels: a dictionary of labels :return: a new dictionary with sanitized keys/values """ new = {} for key, value in compat.iteritems(labels): if not isinstance(value, LABEL_TYPES): value = keyword_field(compat.text_type(value)) new[LABEL_RE.sub("_", compat.text_type(key))] = value return new
def __call__(self, value, field_name): value = compat.text_type(value) match = re.match(self.regex, value) if match: return value raise ConfigurationError( "{} does not match pattern {}".format(value, self.verbose_pattern), field_name)
def force_text(s, encoding="utf-8", strings_only=False, errors="strict"): """ Similar to smart_text, except that lazy instances are resolved to strings, rather than kept as lazy objects. If strings_only is True, don't convert (some) non-string-like objects. """ # Handle the common case first, saves 30-40% when s is an instance of # compat.text_type. This function gets called often in that setting. # # Adapted from Django if isinstance(s, compat.text_type): return s if strings_only and is_protected_type(s): return s try: if not isinstance(s, compat.string_types): if hasattr(s, "__unicode__"): s = s.__unicode__() else: if compat.PY3: if isinstance(s, bytes): s = compat.text_type(s, encoding, errors) else: s = compat.text_type(s) else: s = compat.text_type(bytes(s), encoding, errors) else: # Note: We use .decode() here, instead of compat.text_type(s, encoding, # errors), so that if s is a SafeBytes, it ends up being a # SafeText at the end. s = s.decode(encoding, errors) except UnicodeDecodeError as e: if not isinstance(s, Exception): raise UnicodeDecodeError(*e.args) else: # If we get to here, the caller has passed in an Exception # subclass populated with non-ASCII bytestring data without a # working unicode method. Try to handle this without raising a # further exception by individually forcing the exception args # to unicode. s = " ".join( [force_text(arg, encoding, strings_only, errors) for arg in s]) return s
def to_unicode(value): try: value = compat.text_type(force_text(value)) except (UnicodeEncodeError, UnicodeDecodeError): value = "(Error decoding value)" except Exception: # in some cases we get a different exception try: value = compat.binary_type(repr(type(value))) except Exception: value = "(Error decoding value)" return value
def test_transform_dict_keys_utf8_as_unicode(): x = { compat.text_type("\u05e8\u05d5\u05e0\u05d9\u05ea \u05de\u05d2\u05df"): "bar" } result = transform(x) keys = list(result.keys()) assert len(keys) == 1 assert type(keys[0]), str assert keys[0] == "\u05e8\u05d5\u05e0\u05d9\u05ea \u05de\u05d2\u05df"
def register(self, class_path): """ Register a new metric set :param class_path: a string with the import path of the metricset class """ if class_path in self._metricsets: return else: try: class_obj = import_string(class_path) self._metricsets[class_path] = class_obj(self) except ImportError as e: logger.warning("Could not register %s metricset: %s", class_path, compat.text_type(e))
def get_config(self, current_version=None, keys=None): """ Gets configuration from a remote APM Server :param current_version: version of the current configuration :param keys: a JSON-serializable dict to identify this instance, e.g. { "service": { "name": "foo", "environment": "bar" } } :return: a three-tuple of new version, config dictionary and validity in seconds. Any element of the tuple can be None. """ url = self._config_url data = json_encoder.dumps(keys).encode("utf-8") headers = self._headers.copy() headers[b"Content-Type"] = "application/json" headers.pop(b"Content-Encoding", None) # remove gzip content-encoding header headers.update(self.auth_headers) max_age = 300 if current_version: headers["If-None-Match"] = current_version try: response = self.http.urlopen( "POST", url, body=data, headers=headers, timeout=self._timeout, preload_content=False ) except (urllib3.exceptions.RequestError, urllib3.exceptions.HTTPError) as e: logger.debug("HTTP error while fetching remote config: %s", compat.text_type(e)) return current_version, None, max_age body = response.read() if "Cache-Control" in response.headers: try: max_age = int(next(re.finditer(r"max-age=(\d+)", response.headers["Cache-Control"])).groups()[0]) except StopIteration: logger.debug("Could not parse Cache-Control header: %s", response.headers["Cache-Control"]) if response.status == 304: # config is unchanged, return logger.debug("Configuration unchanged") return current_version, None, max_age elif response.status >= 400: return None, None, max_age if not body: logger.debug("APM Server answered with empty body and status code %s", response.status) return current_version, None, max_age return response.headers.get("Etag"), json_encoder.loads(body.decode("utf-8")), max_age
def test_header_encodings(zuqa_client): """ Tests that headers are encoded as bytestrings. If they aren't, urllib assumes it needs to encode the data as well, which is already a zlib encoded bytestring, and explodes. """ headers = {compat.text_type("X"): compat.text_type("V")} transport = Transport("http://localhost:9999", headers=headers, client=zuqa_client) transport.start_thread() try: with mock.patch("zuqa.transport.http.urllib3.PoolManager.urlopen" ) as mock_urlopen: mock_urlopen.return_value = mock.Mock(status=202) transport.send("") _, args, kwargs = mock_urlopen.mock_calls[0] if compat.PY2: assert isinstance(args[1], compat.binary_type) for k, v in kwargs["headers"].items(): assert isinstance(k, compat.binary_type) assert isinstance(v, compat.binary_type) finally: transport.close()
def __call__(self, value, field_name): value = compat.text_type(value) match = re.match(self.regex, value, re.IGNORECASE) if not match: raise ConfigurationError( "{} does not match pattern {}".format(value, self.verbose_pattern), field_name) val, unit = match.groups() try: val = int(val) * self.unit_multipliers[unit] except KeyError: raise ConfigurationError("{} is not a supported unit".format(unit), field_name) return val
def tag(self, **tags): """ This method is deprecated, please use "label()" instead. Tag this span with one or multiple key/value tags. Both the values should be strings span_obj.tag(key1="value1", key2="value2") Note that keys will be dedotted, replacing dot (.), star (*) and double quote (") with an underscore (_) :param tags: key/value pairs of tags :return: None """ for key in tags.keys(): self.labels[LABEL_RE.sub("_", compat.text_type(key))] = encoding.keyword_field(compat.text_type(tags[key]))
def get_lines_from_file(filename, lineno, context_lines, loader=None, module_name=None): """ Returns context_lines before and after lineno from file. Returns (pre_context_lineno, pre_context, context_line, post_context). """ lineno = lineno - 1 lower_bound = max(0, lineno - context_lines) upper_bound = lineno + context_lines source = None if loader is not None and hasattr(loader, "get_source"): result = get_source_lines_from_loader(loader, module_name, lineno, lower_bound, upper_bound) if result is not None: return result if source is None: try: with open(filename, "rb") as file_obj: encoding = "utf8" # try to find encoding of source file by "coding" header # if none is found, utf8 is used as a fallback for line in itertools.islice(file_obj, 0, 2): match = _coding_re.search(line.decode("utf8")) if match: encoding = match.group(1) break file_obj.seek(0) lines = [ compat.text_type(line, encoding, "replace") for line in itertools.islice(file_obj, lower_bound, upper_bound + 1) ] offset = lineno - lower_bound return ( [l.strip("\r\n") for l in lines[0:offset]], lines[offset].strip("\r\n"), [l.strip("\r\n") for l in lines[offset + 1:]] if len(lines) > offset else [], ) except (OSError, IOError, IndexError): pass return None, None, None
def _validate(self, instance, value): if value is None and self.required: raise ConfigurationError( "Configuration error: value for {} is required.".format( self.dict_key), self.dict_key) if self.validators and value is not None: for validator in self.validators: value = validator(value, self.dict_key) if self.type and value is not None: try: value = self.type(value) except ValueError as e: raise ConfigurationError( "{}: {}".format(self.dict_key, compat.text_type(e)), self.dict_key) instance._errors.pop(self.dict_key, None) return value
def set_tag(self, key, value): if self.is_transaction: if key == "type": self.zuqa_ref.transaction_type = value elif key == "result": self.zuqa_ref.result = value elif key == tags.HTTP_STATUS_CODE: self.zuqa_ref.result = "HTTP {}xx".format( compat.text_type(value)[0]) traces.set_context({"status_code": value}, "response") elif key == "user.id": traces.set_user_context(user_id=value) elif key == "user.username": traces.set_user_context(username=value) elif key == "user.email": traces.set_user_context(email=value) elif key == tags.HTTP_URL: traces.set_context({"url": get_url_dict(value)}, "request") elif key == tags.HTTP_METHOD: traces.set_context({"method": value}, "request") elif key == tags.COMPONENT: traces.set_context({"framework": {"name": value}}, "service") else: self.zuqa_ref.label(**{key: value}) elif not self.is_dropped: if key.startswith("db."): span_context = self.zuqa_ref.context or {} if "db" not in span_context: span_context["db"] = {} if key == tags.DATABASE_STATEMENT: span_context["db"]["statement"] = value elif key == tags.DATABASE_USER: span_context["db"]["user"] = value elif key == tags.DATABASE_TYPE: span_context["db"]["type"] = value self.zuqa_ref.type = "db." + value else: self.zuqa_ref.label(**{key: value}) self.zuqa_ref.context = span_context elif key == tags.SPAN_KIND: self.zuqa_ref.type = value else: self.zuqa_ref.label(**{key: value}) return self
def _emit(self, record): # If there's no exception being processed, # exc_info may be a 3-tuple of None # http://docs.python.org/library/sys.html#sys.exc_info if record.exc_info is True or (record.exc_info and all(record.exc_info)): handler = self.client.get_handler("zuqa.events.Exception") exception = handler.capture(self.client, exc_info=record.exc_info) else: exception = None return self.client.capture_message( param_message={ "message": compat.text_type(record.msg), "params": record.args }, exception=exception, level=LOOKBOOK_LEVELS[record.level], logger_name=record.channel, custom=record.extra, stack=record.kwargs.get("stack"), )
def _emit(self, record, **kwargs): data = {} for k, v in compat.iteritems(record.__dict__): if "." not in k and k not in ("culprit",): continue data[k] = v stack = getattr(record, "stack", None) if stack is True: stack = iter_stack_frames(config=self.client.config) if stack: frames = [] started = False last_mod = "" for item in stack: if isinstance(item, (list, tuple)): frame, lineno = item else: frame, lineno = item, item.f_lineno if not started: f_globals = getattr(frame, "f_globals", {}) module_name = f_globals.get("__name__", "") if last_mod.startswith("logging") and not module_name.startswith("logging"): started = True else: last_mod = module_name continue frames.append((frame, lineno)) stack = frames custom = getattr(record, "data", {}) # Add in all of the data from the record that we aren't already capturing for k in record.__dict__.keys(): if k in ( "stack", "name", "args", "msg", "levelno", "exc_text", "exc_info", "data", "created", "levelname", "msecs", "relativeCreated", ): continue if k.startswith("_"): continue custom[k] = record.__dict__[k] # If there's no exception being processed, # exc_info may be a 3-tuple of None # http://docs.python.org/library/sys.html#sys.exc_info if record.exc_info and all(record.exc_info): handler = self.client.get_handler("zuqa.events.Exception") exception = handler.capture(self.client, exc_info=record.exc_info) else: exception = None return self.client.capture( "Message", param_message={"message": compat.text_type(record.msg), "params": record.args}, stack=stack, custom=custom, exception=exception, level=record.levelno, logger_name=record.name, **kwargs )
class ProxyClient(object): """ A proxy which represents the current client at all times. """ # introspection support: __members__ = property(lambda x: x.__dir__()) # Need to pretend to be the wrapped class, for the sake of objects that care # about this (especially in equality tests) __class__ = property(lambda x: get_client().__class__) __dict__ = property(lambda o: get_client().__dict__) __repr__ = lambda: repr(get_client()) __getattr__ = lambda x, o: getattr(get_client(), o) __setattr__ = lambda x, o, v: setattr(get_client(), o, v) __delattr__ = lambda x, o: delattr(get_client(), o) __lt__ = lambda x, o: get_client() < o __le__ = lambda x, o: get_client() <= o __eq__ = lambda x, o: get_client() == o __ne__ = lambda x, o: get_client() != o __gt__ = lambda x, o: get_client() > o __ge__ = lambda x, o: get_client() >= o if compat.PY2: __cmp__ = lambda x, o: cmp(get_client(), o) # noqa F821 __hash__ = lambda x: hash(get_client()) # attributes are currently not callable # __call__ = lambda x, *a, **kw: get_client()(*a, **kw) __nonzero__ = lambda x: bool(get_client()) __len__ = lambda x: len(get_client()) __getitem__ = lambda x, i: get_client()[i] __iter__ = lambda x: iter(get_client()) __contains__ = lambda x, i: i in get_client() __getslice__ = lambda x, i, j: get_client()[i:j] __add__ = lambda x, o: get_client() + o __sub__ = lambda x, o: get_client() - o __mul__ = lambda x, o: get_client() * o __floordiv__ = lambda x, o: get_client() // o __mod__ = lambda x, o: get_client() % o __divmod__ = lambda x, o: get_client().__divmod__(o) __pow__ = lambda x, o: get_client()**o __lshift__ = lambda x, o: get_client() << o __rshift__ = lambda x, o: get_client() >> o __and__ = lambda x, o: get_client() & o __xor__ = lambda x, o: get_client() ^ o __or__ = lambda x, o: get_client() | o __div__ = lambda x, o: get_client().__div__(o) __truediv__ = lambda x, o: get_client().__truediv__(o) __neg__ = lambda x: -(get_client()) __pos__ = lambda x: +(get_client()) __abs__ = lambda x: abs(get_client()) __invert__ = lambda x: ~(get_client()) __complex__ = lambda x: complex(get_client()) __int__ = lambda x: int(get_client()) if compat.PY2: __long__ = lambda x: long(get_client()) # noqa F821 __float__ = lambda x: float(get_client()) __str__ = lambda x: str(get_client()) __unicode__ = lambda x: compat.text_type(get_client()) __oct__ = lambda x: oct(get_client()) __hex__ = lambda x: hex(get_client()) __index__ = lambda x: get_client().__index__() __coerce__ = lambda x, o: x.__coerce__(x, o) __enter__ = lambda x: x.__enter__() __exit__ = lambda x, *a, **kw: x.__exit__(*a, **kw)
def get_data_from_request(self, request, event_type): result = { "env": dict(get_environ(request.META)), "method": request.method, "socket": { "remote_address": request.META.get("REMOTE_ADDR"), "encrypted": request.is_secure() }, "cookies": dict(request.COOKIES), } if self.config.capture_headers: request_headers = dict(get_headers(request.META)) for key, value in request_headers.items(): if isinstance(value, (int, float)): request_headers[key] = str(value) result["headers"] = request_headers if request.method in constants.HTTP_WITH_BODY: capture_body = self.config.capture_body in ("all", event_type) if not capture_body: result["body"] = "[REDACTED]" else: content_type = request.META.get("CONTENT_TYPE") if content_type == "application/x-www-form-urlencoded": data = compat.multidict_to_dict(request.POST) elif content_type and content_type.startswith( "multipart/form-data"): data = compat.multidict_to_dict(request.POST) if request.FILES: data["_files"] = { field: file.name for field, file in compat.iteritems(request.FILES) } else: try: data = request.body except Exception as e: self.logger.debug("Can't capture request body: %s", compat.text_type(e)) data = "<unavailable>" if data is not None: result["body"] = data if hasattr(request, "get_raw_uri"): # added in Django 1.9 url = request.get_raw_uri() else: try: # Requires host to be in ALLOWED_HOSTS, might throw a # DisallowedHost exception url = request.build_absolute_uri() except DisallowedHost: # We can't figure out the real URL, so we have to set it to # DisallowedHost result["url"] = {"full": "DisallowedHost"} url = None if url: result["url"] = get_url_dict(url) return result
def _labels_to_key(self, labels): return tuple((k, compat.text_type(v)) for k, v in sorted(compat.iteritems(labels)))
def no_error(request, id=None): resp = HttpResponse(compat.text_type(id)) resp["My-Header"] = "foo" return resp