scout-apm-logging 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scout_apm_logging-0.1.2/PKG-INFO +127 -0
- scout_apm_logging-0.1.2/README.md +109 -0
- scout_apm_logging-0.1.2/pyproject.toml +38 -0
- scout_apm_logging-0.1.2/scout_apm_logging/__init__.py +1 -0
- scout_apm_logging-0.1.2/scout_apm_logging/handler.py +122 -0
- scout_apm_logging-0.1.2/scout_apm_logging/otel_logger.py +48 -0
- scout_apm_logging-0.1.2/scout_apm_logging/utils/__init__.py +1 -0
- scout_apm_logging-0.1.2/scout_apm_logging/utils/operation_utils.py +48 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: scout-apm-logging
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Scout APM Python Logging Agent
|
|
5
|
+
Author: Quinn Milionis
|
|
6
|
+
Author-email: quinn@scoutapm.com
|
|
7
|
+
Requires-Python: >=3.9,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Requires-Dist: opentelemetry-api (>=1.26.0,<2.0.0)
|
|
14
|
+
Requires-Dist: opentelemetry-exporter-otlp (>=1.26.0,<2.0.0)
|
|
15
|
+
Requires-Dist: opentelemetry-sdk (>=1.26.0,<2.0.0)
|
|
16
|
+
Requires-Dist: scout-apm (>=3.1.0,<4.0.0)
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# Scout APM Python Logging Agent
|
|
20
|
+
|
|
21
|
+
A Python package for detailed, easy-to-navigate, managed log monitoring. Sign up for an account at https://www.scoutapm.com to start monitoring your logs and application performance in minutes.
|
|
22
|
+
|
|
23
|
+
This is to be used alongside [scout-apm-python](https://github.com/scoutapp/scout_apm_python) package, thusly requires it.
|
|
24
|
+
|
|
25
|
+
To use Scout, you'll need to
|
|
26
|
+
[sign up for an account](https://scoutapm.com/users/sign_up) or use
|
|
27
|
+
[our Heroku Addon](https://devcenter.heroku.com/articles/scout).
|
|
28
|
+
|
|
29
|
+
Then, you'll need to enable logging for your individual apps, which will provide a `SCOUT_LOGS_INGEST_KEY`
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
Install the Scout APM Python Logging Agent using pip:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
pip install scout-apm-python-logging
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Setup and Usage
|
|
40
|
+
|
|
41
|
+
### Using dictConfig
|
|
42
|
+
|
|
43
|
+
We recommend setting up the `OtelScoutHandler` using Python's [`dictConfig`](https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig). Here's an example configuration:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import logging
|
|
47
|
+
from logging.config import dictConfig
|
|
48
|
+
from scout_apm_python_logging import OtelScoutHandler
|
|
49
|
+
|
|
50
|
+
LOGGING_CONFIG = {
|
|
51
|
+
"version": 1,
|
|
52
|
+
"disable_existing_loggers": False,
|
|
53
|
+
"formatters": {
|
|
54
|
+
"standard": {
|
|
55
|
+
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
"handlers": {
|
|
59
|
+
"otel": {
|
|
60
|
+
"level": "DEBUG",
|
|
61
|
+
"class": "scout_apm_python_logging.OtelScoutHandler",
|
|
62
|
+
"service_name": "your-service-name",
|
|
63
|
+
},
|
|
64
|
+
"console": {
|
|
65
|
+
"level": "DEBUG",
|
|
66
|
+
"class": "logging.StreamHandler",
|
|
67
|
+
"formatter": "standard",
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
"loggers": {
|
|
71
|
+
"": { # Root logger
|
|
72
|
+
"handlers": ["console", "otel"],
|
|
73
|
+
"level": "DEBUG",
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Apply the logging configuration
|
|
79
|
+
dictConfig(LOGGING_CONFIG)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Adding to an Existing Logger
|
|
83
|
+
|
|
84
|
+
You can also add the `OtelScoutHandler` to an existing logger:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import logging
|
|
88
|
+
from scout_apm_python_logging import OtelScoutHandler
|
|
89
|
+
|
|
90
|
+
# Get your logger
|
|
91
|
+
logger = logging.getLogger(__name__)
|
|
92
|
+
|
|
93
|
+
# Create and add the OtelScoutHandler
|
|
94
|
+
handler = OtelScoutHandler(service_name="your-service-name")
|
|
95
|
+
logger.addHandler(handler)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Configuration
|
|
99
|
+
|
|
100
|
+
The `OtelScoutHandler` only requires `service_name` to be supplied to the handler as an argument:
|
|
101
|
+
|
|
102
|
+
If the `scout-apm` is [configured](https://scoutapm.com/docs/python#some-configuration-required) (You've set your `SCOUT_KEY`, etc), you'll only need add your `SCOUT_LOGS_INGEST_KEY` to whichever configuration you are already using. This can also be set as an environment variable.
|
|
103
|
+
|
|
104
|
+
Make sure to set the `SCOUT_LOGS_INGEST_KEY` variable before running your application.
|
|
105
|
+
|
|
106
|
+
## OpenTelemetry
|
|
107
|
+
|
|
108
|
+
The Scout APM Python Logging Agent leverages [OpenTelemetry Python](https://github.com/open-telemetry/opentelemetry-python) to provide powerful and standardized logging capabilities. OpenTelemetry is an observability framework for cloud-native software, offering a collection of tools, APIs, and SDKs for generating, collecting, and exporting telemetry data (metrics, logs, and traces).
|
|
109
|
+
|
|
110
|
+
Our `OtelScoutHandler` utilizes the OpenTelemetry Python SDK to:
|
|
111
|
+
|
|
112
|
+
1. Create a `LoggerProvider` with a custom resource that includes your service name and instance ID.
|
|
113
|
+
2. Set up an OTLP (OpenTelemetry Protocol) exporter configured to send logs to Scout's ingestion endpoint.
|
|
114
|
+
3. Add a `BatchLogRecordProcessor` to efficiently process and export log records.
|
|
115
|
+
4. Integrate with Scout APM's request tracking to enrich logs with request-specific information.
|
|
116
|
+
|
|
117
|
+
This integration allows you to benefit from OpenTelemetry's standardized approach to observability while leveraging Scout APM's powerful analysis and visualization tools.
|
|
118
|
+
|
|
119
|
+
For more information about OpenTelemetry and its Python SDK, visit the [OpenTelemetry Python documentation](https://opentelemetry.io/docs/instrumentation/python/).
|
|
120
|
+
|
|
121
|
+
## Additional Resources
|
|
122
|
+
|
|
123
|
+
For more information on using Scout APM and advanced configuration options, please refer to our [documentation](https://docs.scoutapm.com).
|
|
124
|
+
|
|
125
|
+
## Support
|
|
126
|
+
|
|
127
|
+
If you encounter any issues or have questions, please [open an issue](https://github.com/scoutapp/scout-apm-python-logging/issues) on our GitHub repository or contact our support team at support@scoutapm.com.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Scout APM Python Logging Agent
|
|
2
|
+
|
|
3
|
+
A Python package for detailed, easy-to-navigate, managed log monitoring. Sign up for an account at https://www.scoutapm.com to start monitoring your logs and application performance in minutes.
|
|
4
|
+
|
|
5
|
+
This is to be used alongside [scout-apm-python](https://github.com/scoutapp/scout_apm_python) package, thusly requires it.
|
|
6
|
+
|
|
7
|
+
To use Scout, you'll need to
|
|
8
|
+
[sign up for an account](https://scoutapm.com/users/sign_up) or use
|
|
9
|
+
[our Heroku Addon](https://devcenter.heroku.com/articles/scout).
|
|
10
|
+
|
|
11
|
+
Then, you'll need to enable logging for your individual apps, which will provide a `SCOUT_LOGS_INGEST_KEY`
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Install the Scout APM Python Logging Agent using pip:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
pip install scout-apm-python-logging
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Setup and Usage
|
|
22
|
+
|
|
23
|
+
### Using dictConfig
|
|
24
|
+
|
|
25
|
+
We recommend setting up the `OtelScoutHandler` using Python's [`dictConfig`](https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig). Here's an example configuration:
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import logging
|
|
29
|
+
from logging.config import dictConfig
|
|
30
|
+
from scout_apm_python_logging import OtelScoutHandler
|
|
31
|
+
|
|
32
|
+
LOGGING_CONFIG = {
|
|
33
|
+
"version": 1,
|
|
34
|
+
"disable_existing_loggers": False,
|
|
35
|
+
"formatters": {
|
|
36
|
+
"standard": {
|
|
37
|
+
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
"handlers": {
|
|
41
|
+
"otel": {
|
|
42
|
+
"level": "DEBUG",
|
|
43
|
+
"class": "scout_apm_python_logging.OtelScoutHandler",
|
|
44
|
+
"service_name": "your-service-name",
|
|
45
|
+
},
|
|
46
|
+
"console": {
|
|
47
|
+
"level": "DEBUG",
|
|
48
|
+
"class": "logging.StreamHandler",
|
|
49
|
+
"formatter": "standard",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
"loggers": {
|
|
53
|
+
"": { # Root logger
|
|
54
|
+
"handlers": ["console", "otel"],
|
|
55
|
+
"level": "DEBUG",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Apply the logging configuration
|
|
61
|
+
dictConfig(LOGGING_CONFIG)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Adding to an Existing Logger
|
|
65
|
+
|
|
66
|
+
You can also add the `OtelScoutHandler` to an existing logger:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import logging
|
|
70
|
+
from scout_apm_python_logging import OtelScoutHandler
|
|
71
|
+
|
|
72
|
+
# Get your logger
|
|
73
|
+
logger = logging.getLogger(__name__)
|
|
74
|
+
|
|
75
|
+
# Create and add the OtelScoutHandler
|
|
76
|
+
handler = OtelScoutHandler(service_name="your-service-name")
|
|
77
|
+
logger.addHandler(handler)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Configuration
|
|
81
|
+
|
|
82
|
+
The `OtelScoutHandler` only requires `service_name` to be supplied to the handler as an argument:
|
|
83
|
+
|
|
84
|
+
If the `scout-apm` is [configured](https://scoutapm.com/docs/python#some-configuration-required) (You've set your `SCOUT_KEY`, etc), you'll only need add your `SCOUT_LOGS_INGEST_KEY` to whichever configuration you are already using. This can also be set as an environment variable.
|
|
85
|
+
|
|
86
|
+
Make sure to set the `SCOUT_LOGS_INGEST_KEY` variable before running your application.
|
|
87
|
+
|
|
88
|
+
## OpenTelemetry
|
|
89
|
+
|
|
90
|
+
The Scout APM Python Logging Agent leverages [OpenTelemetry Python](https://github.com/open-telemetry/opentelemetry-python) to provide powerful and standardized logging capabilities. OpenTelemetry is an observability framework for cloud-native software, offering a collection of tools, APIs, and SDKs for generating, collecting, and exporting telemetry data (metrics, logs, and traces).
|
|
91
|
+
|
|
92
|
+
Our `OtelScoutHandler` utilizes the OpenTelemetry Python SDK to:
|
|
93
|
+
|
|
94
|
+
1. Create a `LoggerProvider` with a custom resource that includes your service name and instance ID.
|
|
95
|
+
2. Set up an OTLP (OpenTelemetry Protocol) exporter configured to send logs to Scout's ingestion endpoint.
|
|
96
|
+
3. Add a `BatchLogRecordProcessor` to efficiently process and export log records.
|
|
97
|
+
4. Integrate with Scout APM's request tracking to enrich logs with request-specific information.
|
|
98
|
+
|
|
99
|
+
This integration allows you to benefit from OpenTelemetry's standardized approach to observability while leveraging Scout APM's powerful analysis and visualization tools.
|
|
100
|
+
|
|
101
|
+
For more information about OpenTelemetry and its Python SDK, visit the [OpenTelemetry Python documentation](https://opentelemetry.io/docs/instrumentation/python/).
|
|
102
|
+
|
|
103
|
+
## Additional Resources
|
|
104
|
+
|
|
105
|
+
For more information on using Scout APM and advanced configuration options, please refer to our [documentation](https://docs.scoutapm.com).
|
|
106
|
+
|
|
107
|
+
## Support
|
|
108
|
+
|
|
109
|
+
If you encounter any issues or have questions, please [open an issue](https://github.com/scoutapp/scout-apm-python-logging/issues) on our GitHub repository or contact our support team at support@scoutapm.com.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "scout-apm-logging"
|
|
3
|
+
version = "0.1.2"
|
|
4
|
+
description = "Scout APM Python Logging Agent"
|
|
5
|
+
authors = ["Quinn Milionis <quinn@scoutapm.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
|
|
8
|
+
[tool.poetry.dependencies]
|
|
9
|
+
python = "^3.9"
|
|
10
|
+
opentelemetry-api = "^1.26.0"
|
|
11
|
+
opentelemetry-sdk = "^1.26.0"
|
|
12
|
+
opentelemetry-exporter-otlp = "^1.26.0"
|
|
13
|
+
scout-apm = "^3.1.0"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
[tool.poetry.group.dev.dependencies]
|
|
17
|
+
taskipy = "^1.13.0"
|
|
18
|
+
pytest = "^8.3.2"
|
|
19
|
+
coverage = "^7.6.1"
|
|
20
|
+
black = "^24.8.0"
|
|
21
|
+
pytest-cov = "^5.0.0"
|
|
22
|
+
flake8 = "^7.1.1"
|
|
23
|
+
flake8-black = "^0.3.6"
|
|
24
|
+
mypy = "^1.11.1"
|
|
25
|
+
flask = "^3.0.3"
|
|
26
|
+
|
|
27
|
+
[tool.taskipy.tasks]
|
|
28
|
+
test = "pytest . --cov-report=term-missing --cov=."
|
|
29
|
+
lint = "task flake8 && task mypy"
|
|
30
|
+
flake8 = "flake8 ."
|
|
31
|
+
black = "black ."
|
|
32
|
+
mypy = "poetry run mypy . --install-types --non-interactive --ignore-missing-imports"
|
|
33
|
+
format = "task black"
|
|
34
|
+
check = "task format && task lint && task test"
|
|
35
|
+
|
|
36
|
+
[build-system]
|
|
37
|
+
requires = ["poetry-core"]
|
|
38
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from scout_apm_logging.handler import OtelScoutHandler
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import threading
|
|
4
|
+
from opentelemetry import _logs
|
|
5
|
+
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
|
|
6
|
+
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
|
|
7
|
+
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
|
|
8
|
+
from opentelemetry.sdk.resources import Resource
|
|
9
|
+
from scout_apm.core.tracked_request import TrackedRequest
|
|
10
|
+
from scout_apm.core import scout_config
|
|
11
|
+
from scout_apm_logging.utils.operation_utils import get_operation_detail
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class OtelScoutHandler(logging.Handler):
|
|
15
|
+
def __init__(self, service_name):
|
|
16
|
+
super().__init__()
|
|
17
|
+
self.logger_provider = None
|
|
18
|
+
self.service_name = self._get_service_name(service_name)
|
|
19
|
+
self.ingest_key = self._get_ingest_key()
|
|
20
|
+
self.endpoint = self._get_endpoint()
|
|
21
|
+
self.setup_logging()
|
|
22
|
+
self._handling_log = threading.local()
|
|
23
|
+
|
|
24
|
+
def setup_logging(self):
|
|
25
|
+
self.logger_provider = LoggerProvider(
|
|
26
|
+
resource=Resource.create(
|
|
27
|
+
{
|
|
28
|
+
"service.name": self.service_name,
|
|
29
|
+
"service.instance.id": os.uname().nodename,
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
_logs.set_logger_provider(self.logger_provider)
|
|
34
|
+
|
|
35
|
+
otlp_exporter = OTLPLogExporter(
|
|
36
|
+
headers={"x-telemetryhub-key": self.ingest_key},
|
|
37
|
+
endpoint=self.endpoint,
|
|
38
|
+
)
|
|
39
|
+
self.logger_provider.add_log_record_processor(
|
|
40
|
+
BatchLogRecordProcessor(otlp_exporter)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
self.otel_handler = LoggingHandler(
|
|
44
|
+
level=logging.NOTSET, logger_provider=self.logger_provider
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def emit(self, record):
|
|
48
|
+
if getattr(self._handling_log, "value", False):
|
|
49
|
+
# We're already handling a log message, don't try to get the TrackedRequest
|
|
50
|
+
return self.otel_handler.emit(record)
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
self._handling_log.value = True
|
|
54
|
+
scout_request = TrackedRequest.instance()
|
|
55
|
+
|
|
56
|
+
if scout_request:
|
|
57
|
+
operation_detail = get_operation_detail(scout_request)
|
|
58
|
+
if operation_detail:
|
|
59
|
+
setattr(
|
|
60
|
+
record,
|
|
61
|
+
operation_detail.entrypoint_attribute,
|
|
62
|
+
operation_detail.name,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Add Scout-specific attributes to the log record
|
|
66
|
+
record.scout_request_id = scout_request.request_id
|
|
67
|
+
record.scout_start_time = scout_request.start_time.isoformat()
|
|
68
|
+
if scout_request.end_time:
|
|
69
|
+
record.scout_end_time = scout_request.end_time.isoformat()
|
|
70
|
+
|
|
71
|
+
# Add duration if the request is completed
|
|
72
|
+
if scout_request.end_time:
|
|
73
|
+
record.scout_duration = (
|
|
74
|
+
scout_request.end_time - scout_request.start_time
|
|
75
|
+
).total_seconds()
|
|
76
|
+
|
|
77
|
+
setattr(record, "service.name", self.service_name)
|
|
78
|
+
|
|
79
|
+
# Add tags
|
|
80
|
+
for key, value in scout_request.tags.items():
|
|
81
|
+
setattr(record, f"scout_tag_{key}", value)
|
|
82
|
+
|
|
83
|
+
# Add the current span's operation if available
|
|
84
|
+
current_span = scout_request.current_span()
|
|
85
|
+
if current_span:
|
|
86
|
+
record.scout_current_operation = current_span.operation
|
|
87
|
+
|
|
88
|
+
self.otel_handler.emit(record)
|
|
89
|
+
except Exception as e:
|
|
90
|
+
print(f"Error in OtelScoutHandler.emit: {e}")
|
|
91
|
+
finally:
|
|
92
|
+
self._handling_log.value = False
|
|
93
|
+
|
|
94
|
+
def close(self):
|
|
95
|
+
if self.logger_provider:
|
|
96
|
+
self.logger_provider.shutdown()
|
|
97
|
+
super().close()
|
|
98
|
+
|
|
99
|
+
# These getters will be replaced by a config module to read these values
|
|
100
|
+
# from a config file or environment variables as the Scout APM agent does.
|
|
101
|
+
|
|
102
|
+
def _get_service_name(self, provided_name):
|
|
103
|
+
if provided_name:
|
|
104
|
+
return provided_name
|
|
105
|
+
|
|
106
|
+
# Try to get the name from Scout APM config
|
|
107
|
+
scout_name = scout_config.value("name")
|
|
108
|
+
if scout_name:
|
|
109
|
+
return scout_name
|
|
110
|
+
|
|
111
|
+
return "unnamed-service"
|
|
112
|
+
|
|
113
|
+
def _get_endpoint(self):
|
|
114
|
+
return (
|
|
115
|
+
scout_config.value("logs_reporting_endpoint") or "otlp.scoutotel.com:4317"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def _get_ingest_key(self):
|
|
119
|
+
ingest_key = scout_config.value("logs_ingest_key")
|
|
120
|
+
if not ingest_key:
|
|
121
|
+
raise ValueError("SCOUT_LOGS_INGEST_KEY is not set")
|
|
122
|
+
return ingest_key
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from opentelemetry._logs import set_logger_provider
|
|
4
|
+
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
|
|
5
|
+
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
|
|
6
|
+
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
|
|
7
|
+
from opentelemetry.sdk.resources import Resource
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OTELLoggingManager:
|
|
11
|
+
def __init__(self, service_name):
|
|
12
|
+
self.service_name = service_name
|
|
13
|
+
self.logger_provider = None
|
|
14
|
+
self.logs_reporting_endpoint = "https://otlp.scoutotel.com:4317"
|
|
15
|
+
|
|
16
|
+
# placeholder, we will want to get this from a config or env var.
|
|
17
|
+
self.ingest_key = "aaaa-bbbb-cccc-dddd"
|
|
18
|
+
|
|
19
|
+
def setup_logging(self):
|
|
20
|
+
self.logger_provider = LoggerProvider(
|
|
21
|
+
resource=Resource.create({"service.name": self.service_name})
|
|
22
|
+
)
|
|
23
|
+
set_logger_provider(self.logger_provider)
|
|
24
|
+
|
|
25
|
+
otlp_exporter = OTLPLogExporter(
|
|
26
|
+
headers={"x-telemetryhub-key": self.ingest_key},
|
|
27
|
+
endpoint=self.logs_reporting_endpoint,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
self.logger_provider.add_log_record_processor(
|
|
31
|
+
BatchLogRecordProcessor(otlp_exporter)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
handler = LoggingHandler(
|
|
35
|
+
level=logging.NOTSET, logger_provider=self.logger_provider
|
|
36
|
+
)
|
|
37
|
+
# add otel logging handler to root logger
|
|
38
|
+
logging.getLogger().addHandler(handler)
|
|
39
|
+
|
|
40
|
+
def shutdown(self):
|
|
41
|
+
if self.logger_provider:
|
|
42
|
+
self.logger_provider.shutdown()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def initialize_otel_logging(service_name):
|
|
46
|
+
manager = OTELLoggingManager(service_name)
|
|
47
|
+
manager.setup_logging()
|
|
48
|
+
return manager
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .operation_utils import get_operation_detail
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from scout_apm.core.tracked_request import TrackedRequest
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class OperationDetail:
|
|
8
|
+
name: str
|
|
9
|
+
type: str
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def entrypoint_attribute(self) -> str:
|
|
13
|
+
return f"{self.type}_entrypoint"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OperationType:
|
|
17
|
+
CONTROLLER = "controller"
|
|
18
|
+
JOB = "job"
|
|
19
|
+
CUSTOM = "custom"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
OPERATION_PREFIXES = {
|
|
23
|
+
"Controller/": OperationType.CONTROLLER,
|
|
24
|
+
"Job/": OperationType.JOB,
|
|
25
|
+
"Custom/": OperationType.CUSTOM,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_operation_detail(record: TrackedRequest) -> Optional[OperationDetail]:
|
|
30
|
+
def extract_operation(operation: str) -> Optional[OperationDetail]:
|
|
31
|
+
for prefix, op_type in OPERATION_PREFIXES.items():
|
|
32
|
+
if operation.startswith(prefix):
|
|
33
|
+
return OperationDetail(name=operation[len(prefix) :], type=op_type)
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
# Check the operation attribute first
|
|
37
|
+
operation = getattr(record, "operation", None)
|
|
38
|
+
if operation:
|
|
39
|
+
return extract_operation(operation)
|
|
40
|
+
|
|
41
|
+
# Fall back to checking spans
|
|
42
|
+
spans = getattr(record, "complete_spans", None) or []
|
|
43
|
+
for span in reversed(spans):
|
|
44
|
+
result = extract_operation(span.operation)
|
|
45
|
+
if result:
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
return None
|