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
5 changes: 3 additions & 2 deletions src/instana/agent/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@
import os
from datetime import datetime
from typing import Any, Dict, List, Optional, Union
from requests import Response

import requests
import urllib3
from requests import Response

from instana.agent.base import BaseAgent
from instana.collector.host import HostCollector
from instana.fsm import Discovery, TheMachine
from instana.log import logger
from instana.options import StandardOptions
from instana.util import to_json
from instana.util.runtime import get_py_source
from instana.util.runtime import get_py_source, log_runtime_env_info
from instana.util.span_utils import get_operation_specifiers
from instana.version import VERSION

Expand Down Expand Up @@ -62,6 +62,7 @@ def __init__(self) -> None:
logger.info(
f"Stan is on the scene. Starting Instana instrumentation version: {VERSION}"
)
log_runtime_env_info()

self.collector = HostCollector(self)
self.machine = TheMachine(self)
Expand Down
34 changes: 19 additions & 15 deletions src/instana/instrumentation/cassandra.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@
https://github.com/datastax/python-driver
"""

from typing import Any, Callable, Dict, Tuple
import wrapt
from instana.log import logger
from instana.span.span import InstanaSpan
from instana.util.traceutils import get_tracer_tuple, tracing_is_off

try:
from typing import TYPE_CHECKING, Any, Callable, Dict, Tuple

import cassandra
from cassandra.cluster import ResponseFuture, Session
import wrapt

from instana.log import logger
from instana.util.traceutils import get_tracer_tuple, tracing_is_off

if TYPE_CHECKING:
from cassandra.cluster import ResponseFuture, Session

from instana.span.span import InstanaSpan

consistency_levels = dict(
{
Expand All @@ -34,8 +38,8 @@
)

def collect_attributes(
span: InstanaSpan,
fn: ResponseFuture,
span: "InstanaSpan",
fn: "ResponseFuture",
) -> None:
tried_hosts = []
for host in fn.attempted_hosts:
Expand All @@ -50,23 +54,23 @@ def collect_attributes(

def cb_request_finish(
_,
span: InstanaSpan,
fn: ResponseFuture,
span: "InstanaSpan",
fn: "ResponseFuture",
) -> None:
collect_attributes(span, fn)
span.end()

def cb_request_error(
results: Dict[str, Any],
span: InstanaSpan,
fn: ResponseFuture,
span: "InstanaSpan",
fn: "ResponseFuture",
) -> None:
collect_attributes(span, fn)
span.mark_as_errored({"cassandra.error": results.summary})
span.end()

def request_init_with_instana(
fn: ResponseFuture,
fn: "ResponseFuture",
) -> None:
tracer, parent_span, _ = get_tracer_tuple()
parent_context = parent_span.get_span_context() if parent_span else None
Expand Down Expand Up @@ -95,7 +99,7 @@ def request_init_with_instana(
@wrapt.patch_function_wrapper("cassandra.cluster", "Session.__init__")
def init_with_instana(
wrapped: Callable[..., object],
instance: Session,
instance: "Session",
args: Tuple[object, ...],
kwargs: Dict[str, Any],
) -> object:
Expand Down
3 changes: 2 additions & 1 deletion src/instana/instrumentation/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import Any, Tuple, Dict, Callable

from instana.log import logger
from instana.util.runtime import get_runtime_env_info
from instana.util.traceutils import get_tracer_tuple, tracing_is_off


Expand All @@ -25,7 +26,7 @@ def log_with_instana(

# We take into consideration if `stacklevel` is already present in `kwargs`.
# This prevents the error `_log() got multiple values for keyword argument 'stacklevel'`
stacklevel_in = kwargs.pop("stacklevel", 1)
stacklevel_in = kwargs.pop("stacklevel", 1 if get_runtime_env_info()[0] != "ppc64le" else 2)
stacklevel = stacklevel_in + 1 + (sys.version_info >= (3, 14))

try:
Expand Down
99 changes: 84 additions & 15 deletions src/instana/util/runtime.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
# (c) Copyright IBM Corp. 2021
# (c) Copyright Instana Inc. 2020

import re
import os
import platform
import re
import sys
from typing import Dict, List, Tuple, Union

from ..log import logger
from instana.log import logger

def get_py_source(filename):
"""
Retrieves and returns the source code for any Python
files requested by the UI via the host agent

@param filename [String] The fully qualified path to a file
def get_py_source(filename: str) -> Dict[str, str]:
"""
Retrieves the source code for Python files requested by the UI via the host agent.

This function reads and returns the content of Python source files. It validates
that the requested file has a .py extension and returns an appropriate error
message if the file cannot be read or is not a Python file.

Args:
filename (str): The fully qualified path to a Python source file

Returns:
Dict[str, str]: A dictionary containing either:
- {"data": source_code} if successful
- {"error": error_message} if an error occurred
"""
response = None
try:
Expand All @@ -35,9 +47,24 @@ def get_py_source(filename):
regexp_py = re.compile(r"\.py$")


def determine_service_name():
""" This function makes a best effort to name this application process. """

def determine_service_name() -> str:
"""
Determines the most appropriate service name for this application process.

The service name is determined using the following priority order:
1. INSTANA_SERVICE_NAME environment variable if set
2. For specific frameworks:
- For gunicorn: process title or "gunicorn"
- For Flask: FLASK_APP environment variable
- For Django: first part of DJANGO_SETTINGS_MODULE
- For uwsgi: "uWSGI master/worker [app_name]"
3. Command line arguments (first non-option argument)
4. Executable name
5. "python" as a fallback

Returns:
str: The determined service name
"""
# One environment variable to rule them all
if "INSTANA_SERVICE_NAME" in os.environ:
return os.environ["INSTANA_SERVICE_NAME"]
Expand Down Expand Up @@ -115,11 +142,22 @@ def determine_service_name():

return app_name

def get_proc_cmdline(as_string=False):
def get_proc_cmdline(as_string: bool = False) -> Union[List[str], str]:
"""
Parse the proc file system for the command line of this process. If not available, then return a default.
Return is dependent on the value of `as_string`. If True, return the full command line as a string,
otherwise a list.
Parses the process command line from the proc file system.

This function attempts to read the command line of the current process from
/proc/self/cmdline. If the proc filesystem is not available (e.g., on non-Unix
systems), it returns a default value.

Args:
as_string (bool, optional): If True, returns the command line as a single
space-separated string. If False, returns a list
of command line arguments. Defaults to False.

Returns:
Union[List[str], str]: The command line as either a list of arguments or a
space-separated string, depending on the as_string parameter.
"""
name = "python"
if os.path.isfile("/proc/self/cmdline"):
Expand All @@ -140,4 +178,35 @@ def get_proc_cmdline(as_string=False):
if as_string is True:
parts = " ".join(parts)

return parts
return parts


def get_runtime_env_info() -> Tuple[str, str]:
"""
Returns information about the current runtime environment.

This function collects and returns details about the machine architecture
and Python version being used by the application.

Returns:
Tuple[str, str]: A tuple containing:
- Machine type (e.g., 'arm64', 'ppc64le')
- Python version string
"""
machine = platform.machine()
python_version = platform.python_version()

return machine, python_version


def log_runtime_env_info() -> None:
"""
Logs debug information about the current runtime environment.

This function retrieves machine architecture and Python version information
using get_runtime_env_info() and logs it as a debug message.
"""
machine, python_version = get_runtime_env_info()
logger.debug(f"Runtime environment: Machine: {machine}, Python version: {python_version}")

# Made with Bob
4 changes: 4 additions & 0 deletions tests/clients/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import pytest
from opentelemetry.trace import SpanKind

from instana.util.runtime import get_runtime_env_info
from instana.singletons import agent, tracer


Expand Down Expand Up @@ -146,6 +147,9 @@ def test_log_caller_with_stacklevel(
)
self.logger.addHandler(handler)

if get_runtime_env_info()[0] == "ppc64le":
stacklevel += 1

def log_custom_warning():
self.logger.warning("foo %s", "bar", stacklevel=stacklevel)

Expand Down
23 changes: 22 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,21 @@
from instana.span.span import InstanaSpan
from instana.span_context import SpanContext
from instana.tracer import InstanaTracerProvider
from instana.util.runtime import get_runtime_env_info

collect_ignore_glob = [
"*test_gevent*",
"*collector/test_gcr*",
"*agent/test_google*",
]

# ppc64le has limitations with some supported libraries.
machine, py_version = get_runtime_env_info()
if machine == "ppc64le":
collect_ignore_glob.append("*test_grpcio*")
collect_ignore_glob.append("*test_google-cloud*")
collect_ignore_glob.append("*test_pymongo*")

# # Cassandra and gevent tests are run in dedicated jobs on CircleCI and will
# # be run explicitly. (So always exclude them here)
if not os.environ.get("CASSANDRA_TEST"):
Expand All @@ -55,7 +63,7 @@
collect_ignore_glob.append("*test_fastapi*")
# aiohttp-server tests failing due to deprecated methods used
collect_ignore_glob.append("*test_aiohttp_server*")
# Currently Saniic does not support python >= 3.14
# Currently Sanic does not support python >= 3.14
collect_ignore_glob.append("*test_sanic*")


Expand Down Expand Up @@ -237,3 +245,16 @@ def announce(monkeypatch, request) -> None:
monkeypatch.setattr(HostAgent, "announce", HostAgent.announce)
else:
monkeypatch.setattr(HostAgent, "announce", always_true)

# Mocking the import of uwsgi
def _uwsgi_masterpid() -> int:
return 12345

module = type(sys)("uwsgi")
module.opt = {
"master": True,
"lazy-apps": True,
"enable-threads": True,
}
module.masterpid = _uwsgi_masterpid
sys.modules["uwsgi"] = module
Loading
Loading