|
| 1 | +import json |
1 | 2 | import os
|
| 3 | +import time |
2 | 4 | import uuid
|
3 | 5 | import random
|
4 | 6 | import socket
|
| 7 | +import logging |
5 | 8 | from collections.abc import Mapping
|
6 | 9 | from datetime import datetime, timezone
|
7 | 10 | from importlib import import_module
|
|
55 | 58 | from typing import Union
|
56 | 59 | from typing import TypeVar
|
57 | 60 |
|
58 |
| - from sentry_sdk._types import Event, Hint, SDKInfo |
| 61 | + from sentry_sdk._types import Event, Hint, SDKInfo, Log |
59 | 62 | from sentry_sdk.integrations import Integration
|
60 | 63 | from sentry_sdk.metrics import MetricsAggregator
|
61 | 64 | from sentry_sdk.scope import Scope
|
@@ -206,6 +209,10 @@ def capture_event(self, *args, **kwargs):
|
206 | 209 | # type: (*Any, **Any) -> Optional[str]
|
207 | 210 | return None
|
208 | 211 |
|
| 212 | + def capture_log(self, scope, severity_text, severity_number, template, **kwargs): |
| 213 | + # type: (Scope, str, int, str, **Any) -> None |
| 214 | + pass |
| 215 | + |
209 | 216 | def capture_session(self, *args, **kwargs):
|
210 | 217 | # type: (*Any, **Any) -> None
|
211 | 218 | return None
|
@@ -847,6 +854,110 @@ def capture_event(
|
847 | 854 |
|
848 | 855 | return return_value
|
849 | 856 |
|
| 857 | + def capture_log(self, scope, severity_text, severity_number, template, **kwargs): |
| 858 | + # type: (Scope, str, int, str, **Any) -> None |
| 859 | + logs_enabled = self.options["_experiments"].get("enable_sentry_logs", False) |
| 860 | + if not logs_enabled: |
| 861 | + return |
| 862 | + |
| 863 | + headers = { |
| 864 | + "sent_at": format_timestamp(datetime.now(timezone.utc)), |
| 865 | + } # type: dict[str, object] |
| 866 | + |
| 867 | + attrs = { |
| 868 | + "sentry.message.template": template, |
| 869 | + } # type: dict[str, str | bool | float | int] |
| 870 | + |
| 871 | + kwargs_attributes = kwargs.get("attributes") |
| 872 | + if kwargs_attributes is not None: |
| 873 | + attrs.update(kwargs_attributes) |
| 874 | + |
| 875 | + environment = self.options.get("environment") |
| 876 | + if environment is not None: |
| 877 | + attrs["sentry.environment"] = environment |
| 878 | + |
| 879 | + release = self.options.get("release") |
| 880 | + if release is not None: |
| 881 | + attrs["sentry.release"] = release |
| 882 | + |
| 883 | + span = scope.span |
| 884 | + if span is not None: |
| 885 | + attrs["sentry.trace.parent_span_id"] = span.span_id |
| 886 | + |
| 887 | + for k, v in kwargs.items(): |
| 888 | + attrs[f"sentry.message.parameters.{k}"] = v |
| 889 | + |
| 890 | + log = { |
| 891 | + "severity_text": severity_text, |
| 892 | + "severity_number": severity_number, |
| 893 | + "body": template.format(**kwargs), |
| 894 | + "attributes": attrs, |
| 895 | + "time_unix_nano": time.time_ns(), |
| 896 | + "trace_id": None, |
| 897 | + } # type: Log |
| 898 | + |
| 899 | + # If debug is enabled, log the log to the console |
| 900 | + debug = self.options.get("debug", False) |
| 901 | + if debug: |
| 902 | + severity_text_to_logging_level = { |
| 903 | + "trace": logging.DEBUG, |
| 904 | + "debug": logging.DEBUG, |
| 905 | + "info": logging.INFO, |
| 906 | + "warn": logging.WARNING, |
| 907 | + "error": logging.ERROR, |
| 908 | + "fatal": logging.CRITICAL, |
| 909 | + } |
| 910 | + logger.log( |
| 911 | + severity_text_to_logging_level.get(severity_text, logging.DEBUG), |
| 912 | + f'[Sentry Logs] {log["body"]}', |
| 913 | + ) |
| 914 | + |
| 915 | + propagation_context = scope.get_active_propagation_context() |
| 916 | + if propagation_context is not None: |
| 917 | + headers["trace_id"] = propagation_context.trace_id |
| 918 | + log["trace_id"] = propagation_context.trace_id |
| 919 | + |
| 920 | + envelope = Envelope(headers=headers) |
| 921 | + |
| 922 | + before_emit_log = self.options["_experiments"].get("before_emit_log") |
| 923 | + if before_emit_log is not None: |
| 924 | + log = before_emit_log(log, {}) |
| 925 | + if log is None: |
| 926 | + return |
| 927 | + |
| 928 | + def format_attribute(key, val): |
| 929 | + # type: (str, int | float | str | bool) -> Any |
| 930 | + if isinstance(val, bool): |
| 931 | + return {"key": key, "value": {"boolValue": val}} |
| 932 | + if isinstance(val, int): |
| 933 | + return {"key": key, "value": {"intValue": str(val)}} |
| 934 | + if isinstance(val, float): |
| 935 | + return {"key": key, "value": {"doubleValue": val}} |
| 936 | + if isinstance(val, str): |
| 937 | + return {"key": key, "value": {"stringValue": val}} |
| 938 | + return {"key": key, "value": {"stringValue": json.dumps(val)}} |
| 939 | + |
| 940 | + otel_log = { |
| 941 | + "severityText": log["severity_text"], |
| 942 | + "severityNumber": log["severity_number"], |
| 943 | + "body": {"stringValue": log["body"]}, |
| 944 | + "timeUnixNano": str(log["time_unix_nano"]), |
| 945 | + "attributes": [ |
| 946 | + format_attribute(k, v) for (k, v) in log["attributes"].items() |
| 947 | + ], |
| 948 | + } |
| 949 | + |
| 950 | + if "trace_id" in log: |
| 951 | + otel_log["traceId"] = log["trace_id"] |
| 952 | + |
| 953 | + envelope.add_log(otel_log) # TODO: batch these |
| 954 | + |
| 955 | + if self.spotlight: |
| 956 | + self.spotlight.capture_envelope(envelope) |
| 957 | + |
| 958 | + if self.transport is not None: |
| 959 | + self.transport.capture_envelope(envelope) |
| 960 | + |
850 | 961 | def capture_session(
|
851 | 962 | self, session # type: Session
|
852 | 963 | ):
|
|
0 commit comments