Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changesets/add-the-ignore-logs-option.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
bump: minor
type: add
---

Add an `ignore_logs` option, which takes a list of patterns. Log lines that match any of the patterns are not sent to AppSignal. Set it with the `APPSIGNAL_IGNORE_LOGS` environment variable, or as an option on the `Appsignal` client. Read our [ignore logs guide](https://docs.appsignal.com/guides/filter-data/ignore-logs.html) for the patterns that are supported.
Comment thread
unflxw marked this conversation as resolved.

This option only has an effect in collector mode, because that is the only mode in which this package sends logs.
8 changes: 8 additions & 0 deletions .changesets/detect-the-revision-from-the-platform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
bump: patch
type: add
---

Detect the revision that is being deployed from the environment variables set by Heroku, Render, Kamal and Scalingo: `HEROKU_SLUG_COMMIT`, `RENDER_GIT_COMMIT`, `KAMAL_VERSION` and `CONTAINER_VERSION`. Applications deployed on those platforms now report their revision without setting the `revision` configuration option.

This affects collector mode, where deploys were reported as `unknown` when the revision was not configured.
6 changes: 6 additions & 0 deletions .changesets/keep-detected-values-when-an-option-is-none.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
bump: patch
type: fix
---

An option set to `None` when initializing the `Appsignal` client no longer replaces a value that AppSignal detected itself. For example, `Appsignal(hostname=None)` now reports the detected hostname, instead of reporting no hostname at all. Options that AppSignal does not detect are unchanged: setting `request_headers` to `None`, for example, still turns off request header collection.
6 changes: 6 additions & 0 deletions .changesets/prefer-the-dyno-name-for-the-hostname.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
bump: patch
type: change
---

On Heroku, report the name of the dyno as the hostname. Before, the hostname of the container that the dyno runs in was reported, so applications running on Heroku will see their data reported under a new host name.
8 changes: 0 additions & 8 deletions .changesets/report-host-name-when-set-to-none.md

This file was deleted.

6 changes: 6 additions & 0 deletions .changesets/send-the-app-path-to-the-collector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
bump: patch
type: fix
---

In collector mode, backtrace lines from your own application are now shown as paths relative to your application's root, and are recognized as your application's code.
78 changes: 74 additions & 4 deletions src/appsignal/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class Options(TypedDict, total=False):
http_proxy: str | None
ignore_actions: list[str] | None
ignore_errors: list[str] | None
ignore_logs: list[str] | None
ignore_namespaces: list[str] | None
Comment thread
unflxw marked this conversation as resolved.
log: str | None
log_level: str | None
Expand All @@ -48,6 +49,7 @@ class Options(TypedDict, total=False):
nginx_port: str | int | None
opentelemetry_port: str | int | None
name: str | None
platform: str | None
push_api_key: str | None
revision: str | None
request_headers: list[str] | None
Expand Down Expand Up @@ -133,10 +135,11 @@ class Config:

def __init__(self, options: Options | None = None) -> None:
self.valid = False
system = Config.load_from_system()
self.sources = Sources(
default=self.DEFAULT_CONFIG,
system=Config.load_from_system(),
initial=options or Options(),
system=system,
initial=without_none_overrides(options or Options(), system),
environment=Config.load_from_environment(),
)
final_options = Options()
Expand Down Expand Up @@ -170,13 +173,63 @@ def should_use_collector(self) -> bool:
def should_use_external_collector(self) -> bool:
return self.option("collector_endpoint") is not None

# Environment variables that deployment platforms set to the revision that
# is being deployed, in the order the agent reads them. The agent detects
# the revision this way as well, but only for the data it reports itself,
# so the package has to do it for collector mode.
PLATFORM_REVISION_ENVIRONMENT_VARIABLES: ClassVar[list[str]] = [
"HEROKU_SLUG_COMMIT",
"RENDER_GIT_COMMIT",
"KAMAL_VERSION",
"CONTAINER_VERSION", # Scalingo
]

@staticmethod
def load_from_system() -> Options:
return Options(
options = Options(
app_path=os.getcwd(),
hostname=os.environ.get("HOSTNAME") or socket.gethostname(),
# The Heroku dyno name comes first, the way the agent detects the
# hostname. Heroku sets the container hostname as well, and the
# dyno name is the more useful of the two.
hostname=os.environ.get("DYNO")
or os.environ.get("HOSTNAME")
or socket.gethostname(),
)

revision = Config.detect_revision()
if revision is not None:
options["revision"] = revision

detected_platform = Config.detect_platform()
if detected_platform is not None:
options["platform"] = detected_platform

return options

@staticmethod
def detect_revision() -> str | None:
for variable in Config.PLATFORM_REVISION_ENVIRONMENT_VARIABLES:
revision = os.environ.get(variable)
if revision:
return revision

return None

# Detect the platform the application is deployed on, the way the agent
# does. The agent only detects it for the data it reports itself, so the
# package has to do it for collector mode. It is detected rather than
# configured: it has no environment variable of its own, and it is not
# documented as an option.
@staticmethod
def detect_platform() -> str | None:
if os.environ.get("DOKKU_ROOT"):
return "dokku"

if os.environ.get("DYNO"):
return "heroku"

return None

@staticmethod
def load_from_environment() -> Options:
options = Options(
Expand Down Expand Up @@ -224,6 +277,7 @@ def load_from_environment() -> Options:
http_proxy=os.environ.get("APPSIGNAL_HTTP_PROXY"),
ignore_actions=parse_list(os.environ.get("APPSIGNAL_IGNORE_ACTIONS")),
ignore_errors=parse_list(os.environ.get("APPSIGNAL_IGNORE_ERRORS")),
ignore_logs=parse_list(os.environ.get("APPSIGNAL_IGNORE_LOGS")),
Comment thread
unflxw marked this conversation as resolved.
ignore_namespaces=parse_list(os.environ.get("APPSIGNAL_IGNORE_NAMESPACES")),
log=os.environ.get("APPSIGNAL_LOG"),
log_level=os.environ.get("APPSIGNAL_LOG_LEVEL"),
Expand Down Expand Up @@ -311,6 +365,7 @@ def set_private_environ(self) -> None:
"_APPSIGNAL_LOGGING_ENDPOINT": options.get("logging_endpoint"),
"_APPSIGNAL_NGINX_PORT": options.get("nginx_port"),
"_APPSIGNAL_OPENTELEMETRY_PORT": options.get("opentelemetry_port"),
"_APPSIGNAL_PLATFORM": options.get("platform"),
"_APPSIGNAL_PUSH_API_KEY": options.get("push_api_key"),
"_APPSIGNAL_PUSH_API_ENDPOINT": options.get("endpoint"),
"_APPSIGNAL_RUNNING_IN_CONTAINER": bool_to_env_str(
Expand Down Expand Up @@ -433,6 +488,7 @@ def _warn_collector_exclusive_options(self) -> None:
"filter_function_parameters",
"filter_request_payload",
"filter_request_query_parameters",
"ignore_logs",
"response_headers",
"send_function_parameters",
"send_request_payload",
Expand Down Expand Up @@ -498,6 +554,20 @@ def parse_bool(value: str | None) -> bool | None:
return None


# An option passed to the client as None carries no value, so it must not erase
# one that was detected from the system. It does still override a default: that
# is how `request_headers=None` turns off request header collection.
def without_none_overrides(options: Options, system: Options) -> Options:
return cast(
Options,
{
key: value
for key, value in options.items()
if value is not None or key not in system
},
)


def parse_list(value: str | None) -> list[str] | None:
if value is None:
return None
Expand Down
3 changes: 3 additions & 0 deletions src/appsignal/opentelemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,8 @@ def _resource(config: Config) -> Resource:
"appsignal.config.environment": config.options.get("environment"),
"appsignal.config.push_api_key": config.options.get("push_api_key"),
"appsignal.config.revision": config.options.get("revision") or "unknown",
"appsignal.config.app_path": config.options.get("app_path"),
"appsignal.config.platform": config.options.get("platform"),
"appsignal.config.language_integration": "python",
"service.name": config.options.get("service_name") or "app",
"host.name": config.options.get("hostname") or "unknown",
Expand All @@ -310,6 +312,7 @@ def _resource(config: Config) -> Resource:
),
"appsignal.config.ignore_actions": config.options.get("ignore_actions"),
"appsignal.config.ignore_errors": config.options.get("ignore_errors"),
"appsignal.config.ignore_logs": config.options.get("ignore_logs"),
"appsignal.config.ignore_namespaces": config.options.get(
"ignore_namespaces"
),
Expand Down
Loading
Loading